Robot Programming II Documentation
リリース 1.0

NAGASAKA Yasunori

2024 年 08 月 28 日

iContents:
第 1 章 はじめに

1

講義で取り上げる内容、目標 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

1.2

実習の環境 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2

1.3

Linux の基本コマンドの理解度の確認 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3

1.4

C 言語の復習 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

1.5

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4

1.1

第 2 章 コンパイル、実行ファイルの生成

72.1

C と C++の違い

2.2

コンパイラの使い方、オプション . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.3

分割コンパイル、リンク . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

2.4

ヘッダに書く内容、二重定義を防ぐ

2.5

分割コンパイルを支援するツール make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

2.6

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

第 3 章 デバッグの方法、デバッガの使い方

7. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

25

3.1

文法エラーがある場合のデバッグ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.2

文法エラーがない場合のデバッグ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

3.3

printf() を使うデバッグ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

3.4

デバッグ用のツール「デバッガ」を使うデバッグ . . . . . . . . . . . . . . . . . . . . . . . 37

3.5

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

第 4 章 プログラム作成の基本スキル

43

4.1

プログラムを作る能力とは . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

4.2

プログラムの内容を説明するドキュメントの重要性 . . . . . . . . . . . . . . . . . . . . . . 43

4.3

関数名、変数名には意味を持たせる

4.4

グローバル変数とローカル変数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

4.5

インデント、プログラムの整形 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

4.6関数のプロトタイプ宣言 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

4.7

個々の関数に割り当てる機能の考え方 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

4.8

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

第 5 章 C++の様々な機能. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

63

5.1

入出力 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

5.2

スコープ、名前空間, using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

5.3

参照、ポインタとの機能の違い . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

5.4

動的メモリ確保 new, delete、malloc, free . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

5.5定数の定義 const, #deﬁne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

5.6

同じ名前の関数、関数のオーバーロード . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

5.7

関数テンプレート . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

5.8

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

第 6 章 オブジェクト指向 1、class

99

6.1

オブジェクト指向とは . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

6.2

C 言語におけるデータとコードの表現 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

6.3

C++ におけるデータとコードの表現 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

6.4

アクセス指定子 public, private . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

6.5

メンバ関数 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105

6.6

コンストラクタ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

6.7

コンストラクタ（動的メモリ確保）※ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

6.8

デストラクタ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

6.9

デストラクタ（動的メモリ確保）※. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

6.10 コピーコンストラクタ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6.11 コピーコンストラクタ（動的メモリ確保）※. . . . . . . . . . . . . . . . . . . . . . . . . 123

6.12 this, メンバ関数の呼び出し元のオブジェクトを指し示すポインタ . . . . . . . . . . . . . . 129
6.13 演算子のオーバーロード . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.14 演算子のオーバーロード（動的メモリ確保）※ . . . . . . . . . . . . . . . . . . . . . . . . 135
6.15 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
第 7 章 オブジェクト指向 2、継承

151

7.1

継承 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

7.2

アクセス指定子 protected

7.3

継承するクラスの記述 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

7.4関数のオーバーライド . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

7.5

コンストラクタ、デストラクタが実行される順番 . . . . . . . . . . . . . . . . . . . . . . . 157

7.6

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

第 8 章 標準テンプレートライブラリ、STL : Standard Template Library

159

8.1

標準テンプレートライブラリ とは何か . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

8.2

コンテナ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

8.3
8.4

イテレータ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

8.5

関数オブジェクト . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170

8.6

まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

アルゴリズム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

第 9 章 まとめ、練習問題

175

9.1

C++ の基本 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

9.2

class オブジェクト指向 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

9.3

class 継承 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176

9.4

STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

第 10 章 QandA, FAQ, トラブルシューティング179

10.1 履修登録、受講環境 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
10.2 講義資料、サンプルプログラム . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
10.3 Ubuntu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
10.4 コンパイル、デバッグ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
10.5 レポート . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
10.6 成績 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
第 11 章 練習問題の答え

ii

185

第 12 章 マークアップのサンプル レベル１

187

12.1 マークアップのサンプル レベル２ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
第 13 章 rst ファイルのサンプル

191

13.1 タイトル . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
13.2 まとめ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
第 14 章 Indices and tables

193

iii

1

第 1 章 はじめに

1.1 講義で取り上げる内容、目標
ロボットプログラミング II では、C++言語の文法、機能を学び、C++の特徴を活かしたプログラムを作成できるようになることを目指す。そのために、• 講義資料 (この文書) は、講義の進行に合わせて一通り読んで、内容を理解するように努める。
• サンプルプログラムがある場合は、そこで扱う内容の例となっているので一通り読んで理解するように努める。

• 実行してプログラムの動作や結果を確認できる例では、自分で実際に実行してみる。
を続けてほしい。

C++ は C 言語に オブジェクト指向 の考え方、表現法を取り入れた言語である。C 言語の上位互換となっているので、C 言語のプログラムは若干の変更で C++ のプログラムとしてコンパイル、実行できる。
この後のロボットプログラミング II の講義では主要な内容として、• コンパイラの機能と使い方• デバッグの方法、デバッガの使い方• C, C++, プログラム作成の基本スキル• C++ の様々な機能• オブジェクト指向 1、class• オブジェクト指向 2、継承
• 標準テンプレートライブラリ, STL : Standard Template Library• 総合演習課題を取り上げる。
教科書は指定しない。以下にいくつか参考文献、資料を挙げておく。ここに挙げたもの以外でも、多くの書籍、公開されている資料があるので、自分に合っていると思うものを入手して講義の前後で必要に応じて参照するとよい。

Robot Programming II Documentation, リリース 1.0

1.1.1 参考文献
• 「ロベールの C++入門講座」, ロベール, マイナビ, ISBN-13: 978-4-8399-2605-2, 2007 年 11 月,(900 ページを超えるので全体を読み通すのは根性がいるが、説明は大変詳しい。)• 「C++の絵本 第 2 版」, 株式会社アンク, 翔泳社, ISBN-13: 978-4798151908, 2017 年 4 月, (C++の 機
能に絞って説明しているので、C 言語を事前に理解している人向け。200 ページを少し超える程度なので読みやすい。ページ数が少ない分、説明は簡潔である。)• 「C++入門」, http://wisdom.sakura.ne.jp/programming/cpp/ (C++を解説する Web サイトである。
印刷すると 110 ページ程度であるが、一通りの機能がしっかり解説されている。)• 「C 言語経験者のための C++ 入門」, 金森由博, http://kanamori.cs.tsukuba.ac.jp/index-ja.html#for_
students (28 枚のスライドで C++の要点が簡潔に説明されている。C 言語を事前に理解している人向け。すべての機能が含まれるわけではないが、基本はここに書かれている内容でほぼカバーされている。)

1.2 実習の環境
プログラミングの実習は次の環境で行う。

• OS は Ubuntu を使用する。version は 18.04, 20.04 などに対応する。他の version でも明らかな問題がなければ使用可能。

• コンパイラは Ubuntu にインストールされた gcc, g++ を使用する。
コンパイラを含む開発環境がインストールされていない場合は、次のコマンドでインストールする。

$ sudo apt install build-essential
OS の version は上記のどれでもよい。サンプルプログラムの動作確認は 18.04 上で行っている。他の version
では付属するコンパイラの version の差異により、稀ではあるがコンパイル時の挙動や結果に違いが生じる
可能性がある (過去に一度あった) 。明らかに正しいプログラムで問題が生じる場合は担当教員や TA に問い合わせる。
この後の説明において実行するコマンドは次のように表記する。例としてファイルの一覧を表示するコマ
ンド ls では、

$ ls -l
行頭の $ は、ターミナル上のプロンプト (次にコマンドを入力する場所) を表す。
プログラム作成時に使用するエディタは vi, vim, nano, gedit, emacs, vscode など、使い慣れているものでよい。担当教員は emacs を使用するがそれに合わせる必要はない。例として、編集したいファイルを sample.cとすると、$ vim sample.c2

第 1 章 はじめに

Robot Programming II Documentation, リリース 1.0
サンプルプログラムをまとめて圧縮したファイル rp2src.tgz を以下の方法、場所で公開する。内容は同じであるので、どれかを選んで最初の講義の前に自分の PC にダウンロード、展開して参照できるようにしておく。

• 講義資料を置く Web サイト、https://edu.isc.chubu.ac.jp/naga/• Google Classroom のこの講義のクラス• メールに添付して送る。
拡張子 tgz のファイルをダウンロードした後の展開方法は次になる。

• GUI 環境ではダブルクリックして展開する。
• CUI 環境では次のコマンドで展開できる。ファイル名を ﬁle.tgz とすると
$ tar zxvf ./file.tgz

1.3 Linux の基本コマンドの理解度の確認
講義では Linux の端末、ターミナルソフト上でプログラムの編集やコンパイル、実行を行う。その際に Linuxのファイル操作に関する基本コマンドの操作法を理解している必要がある。
★ 練習
章末の練習問題に記されている操作をするためのコマンドを考えてみよう。操作の内容によっては複数のコマンドを組み合わせてもよい。
前提として、• 現在はホームディレクトリ /home/er14000 にいて作業をしている。
• テキストファイル a, b がある。ディレクトリ d1 がある。という状況にあるとする。
各問題ではターミナル上でよく使う機能（ファイル操作、ディレクトリ操作）を

CUI のコマンドでどう記述するかが問われる。登場するのは次のコマンド (mkdir, ls, man, cd, pwd. cat, mv, cp, rm, rmdir) である。
使い方がわからないものはこの機会に調べて覚えておこう。
調べ方は次の方法がある。

• オンラインマニュアル man で調べる。例 $ man mkdir など• 図書館で Linux の入門書を読む。
• WWW の検索サイトで、"linux コマンド" のようなキーワードで調べる• 他の科目の講義資料ページ http://edu.isc.chubu.ac.jp/naga/softa/softac.xml#6 を見る。

1.3. Linux の基本コマンドの理解度の確認

3

Robot Programming II Documentation, リリース 1.0

1.4 C 言語の復習
この講義では、C 言語の基礎は理解しているという前提で、プログラミングや C++に関する講義や実習を
進める。
★ 練習

C 言語の復習のための課題を章末の練習問題として示す。それらの課題を考える過程で、理解が不十分な点があれば早めに復習しておく。

1.5 まとめ
この章の目標• この講義で扱う内容が理解できる。
• 参考文献から自分にあったものを選ぶことができる。
• Linux の基本コマンドの使い方がわかる。
• C 言語の基礎を理解して簡単なプログラムが作成できる。
練習問題

1. C++言語の特徴を簡単に説明しなさい。(e1-1.txt)※ 問題文の右に時々 (e?-?.txt ) のようにファイル名が表示される場合があるが、これは答合わせの時に参照するファイル名なので、それまでは無視してよい。

Linux の基本コマンド
2. ディレクトリ d2 を作る。(e1-2-19.txt)
3. ファイルの一覧を表示する。
4. ファイルの一覧を詳細情報とともに表示する。
5. ファイルの一覧を表示するコマンドをオンラインマニュアルで調べる。
6. d2 に移動する。
7. 現在いるディレクトリを表示する。
8. 一つ上の階層のディレクトリに移動する。
9. d1 に移動する。
10. ホームディレクトリに移動する。
11. a の内容を表示する。
12. a の名前を c に変更する。
13. b を e という名前で複製する。
14. e を d1 に移動させる。

4

第 1 章 はじめに

Robot Programming II Documentation, リリース 1.0
15. b, c を d2 に移動させる。
16. c を削除する。
17. b を d1 に移動させる。
18. d2 を削除する。
19. d1 を削除する。
C 言語の基礎の確認
20. KB(Keyboard) から int n を入力して 1+2+3+...+n を求める。(e1-20.c)
21. KB から int n を入力して奇数か偶数か判定する。(e1-21.c)
22. KB から 文字列を入力して、何文字あるか数える。(e1-22.c)
23. KB から int a, b を入力したら a の b 乗を求める関数 int f(int a, int b) を作る。計算結果は返り値として呼び出し元に返す。(e1-23.c)

1.5. まとめ

5

7

第 2 章 コンパイル、実行ファイルの生成

2.1 C と C++の違い
2.1.1 C++は C 言語の上位互換、拡張子が異なる
C++言語は C 言語の上位互換で、C 言語の機能に加えて多くの機能が拡張されている。よって C 言語で書かれたプログラムは若干の変更で C++のプログラムに変更することができる。ソースファイルの拡張子は

C 言語が .c なのに対して、C++では .cpp .c++ .cxx など複数種類が認められている。一般的に使われるのが.cpp であるのでこの講義では .cpp を採用する。
C 言語のプログラムの例を次に示す。
リスト 2.1: 2-1.c
1
2
3// gcc -Wall -ansi -std=c99 -o 2-1 2-1.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
printf("Hello world. (2-1.c)\n");7
8

exit(EXIT_SUCCESS);

9
10
11

//
}

exit(EXIT_FAILURE);

stdio.h は printf() の定義、stdlib.h は exit() の定義で必要とされる。exit() は返り値を返すとともにプログラ
ム終了時に必要な処理を行う。return() を指定してもよい。その場合、プログラム終了時に必要な処理は

OS が行う。返り値の EXIT_SUCCESS は実行が成功した場合、または正常終了を表す値で実体は整数の 0が定義されている。コメントにしてある EXIT_FAILURE は実行が失敗した場合、または異常終了を表す値
で実体は整数の 1 が定義されている。exit() を使うとそこで実行が終了することが視覚的にわかる。returnを使用しても問題ない。
コンパイル時は以下のように gcc を指定する。これは最も単純なコンパイルの指定で、実際は 2-1.c の 1 行
目のコメントのようにオプションをいくつか指定する (説明は後出) 場合が多い。 ./a.out で実行できる。
★ 練習
コンパイルと実行を試してみよう。

Robot Programming II Documentation, リリース 1.0$ gcc 2-1.c$ ./a.out

2.1.2 ヘッダの指定を変えればコンパイル可能
C++言語のプログラムの例を次に示す。2-1.c のヘッダの指定を変更して、拡張子を.cpp に変更したものである。
リスト 2.2: 2-2.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 2-2 2-2.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
printf("Hello world. (2-2.cpp)\n");7
8

exit(EXIT_SUCCESS);

9
10
11

//
}

exit(EXIT_FAILURE);

コンパイル時は以下のように g++ を指定する。こちらも実際はオプションをいくつか指定する (後出) 場合が多い。

$ g++ 2-2.cpp$ ./a.out
このようにもとが C 言語のプログラムであっても、• ヘッダの指定を変更する。 stdio.h → cstdio• 拡張子を .cpp に変更する。
ことで、C++の機能を内部で全く使っていなくても C++ のプログラムとしてコンパイル、実行することができる。

C++言語の別のプログラムを次に示す。C++固有の記述があるので C 言語とは互換性がない。
リスト 2.3: 2-3.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 2-3 2-3.cpp#include <cstdio>#include <cstdlib>

45class a {int a;

6(次のページに続く)

8

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
7

} b;

8
9

int main(void)

10

{
a c;

11
12

printf("Hello world. (2-3.cpp)\n");13
14

exit(EXIT_SUCCESS);

15
16
17

//
}

exit(EXIT_FAILURE);$ g++ 2-3.cpp$ ./a.out
gcc には .c 、g++ には .cpp を指定するのが本来の使い方であるが、では gcc に .cpp 、g++ には .c を指定したらどうなるか試してみよう。

$ g++ 2-1.c$ gcc 2-2.cpp$ gcc 2-3.cpp
拡張子とプログラムの内容が一致していればエラーにならないことがわかる。これは gcc と g++ は実体が同じであるので、内部で適切な処理に切り替えていると推測できる。
以下では拡張子だけを変更してコピー、コンパイルしてみる。これをすると、拡張子が .c であるが内容にC++の記述が含まれるプログラムになる。
$ cp 2-1.c 2-1.cpp$ gcc 2-1.cpp$ g++ 2-1.cpp$ cp 2-2.cpp 2-2.c$ gcc 2-2.c$ g++ 2-2.c$ cp 2-3.cpp 2-3.c$ gcc 2-3.c$ g++ 2-3.c
この場合はある組み合わせの場合にエラーとなる。gcc に拡張子 .c のファイルを指定するが、内容に C++の記述が含まれるとコンパイルができない。
ここでは、コンパイラ、拡張子、プログラムの内容の組み合わせを様々に組み合わせてその時のコンパイ
ラの動作を確認したが、これはあくまでも実験である。通常このような組み合わせを指定すると、うまく動くこともあるが、トラブルのもとになるので、gcc には C 言語のプログラム、g++ には C++ のプログラ

2.1. C と C++の違い

9Robot Programming II Documentation, リリース 1.0
ムを指定すべきである。

2.1.3 C++ではライブラリの指定が不要
C 言語では、言語の仕様でもともと提供されていない機能を使いたい場合は、その機能の実行可能な機械
語プロブラムをひとまとまりのファイルにパックした ライブラリ というファイルをコンパイル時に指定して実行ファイルに組み込む必要がある。
次のプログラムでは三角関数 sin を計算する数学関数 sin() を利用している。
リスト 2.4: 2-4.c
12
34// gcc -Wall -ansi -std=c99 -o 2-4 2-4.c -lm#include <stdio.h>#include <stdlib.h>#include <math.h>5
6

int main(void)

7

{

8

printf("sin(1.5708) = %f (2-4.c)\n", sin(1.5708));

9

printf("sin(3.1416) = %f\n", sin(3.1416));10exit(EXIT_SUCCESS);

11
12

}
★ 練習
試してみよう。

$ gcc 2-4.c -lm$ ./a.out
関数 sin() を使うには次の２つの指示が必要となる。

• インクルードするヘッダとして math.h を指定する。
• 関数の実体を含むライブラリ libm をコンパイル時に -lm として指定する。
math.h の実体は /usr/include/math.h にある。stdio.h, stdlib.h の実体もここにある。libm の実体は /lib/x86_64linux-gnu/libm.so.6 にある。
関数名がわかれば オンラインマニュアル で入出力の仕様、ヘッダ、ライブラリを調べることができる。man
コマンドは上下の矢印キーで１行ずつスクロール、スペースキーで１ページスクロール、'q' で終了する。
★ 練習
試してみよう。

10

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0$ man sin
$ man printf$ man scanf
C++ では必要なライブラリをコンパイラが管理してくれるのでユーザが指定しなくてもよい。
リスト 2.5: 2-5.cpp
12
3
4// g++ -Wall -ansi -std=c++11 -o 2-5 2-5.cpp#include <cstdio>#include <cstdlib>#include <cmath>5
6

int main(void)

7

{

8

printf("sin(1.5708) = %f (2-5.cpp)\n", sin(1.5708));

9

printf("sin(3.1416) = %f\n", sin(3.1416));10exit(EXIT_SUCCESS);

11
12

}
★ 練習
試してみよう。

$ g++ 2-5.cpp$ ./a.out

2.2 コンパイラの使い方、オプション
gcc, g++ にはコンパイラの動作を指示するための様々なオプションが用意されている。ここではよく使われる代表的なオプションの機能を理解する。
代表的なオプションの機能'-c' コンパイルはするが リンク は行わず、オブジェクトファイルを出力する。分割コンパイルを行う場合に使われる。
'-o' 出力ファイルの名前を指定する。指定しないと実行ファイルは a.out になる。'-c' を指定して出力が実
行ファイルでない場合はソースファイルの拡張子を'.o' に変えたファイル名になる。

'-O' 実行ファイルの速度やサイズを改善するように機械語命令の構成を 最適化 する。最適化のレベルや対象を以下のように指定することができる。

'-O0' : (ラージオーゼロ) コンパイル時間を短くする。速度の改善はしない。デフォルト。
'-O' '-O1' : 速度を改善する。コンパイル時間は長くなり、メモリ使用量が増える。
'-O2' : 更に速度を改善する。コンパイル時間は長くなり、メモリ使用量が増える。
(次のページに続く)

2.2. コンパイラの使い方、オプション

11

Robot Programming II Documentation, リリース 1.0(前のページからの続き)'-O3' : 更に速度を改善する。コンパイル時間は長くなり、メモリ使用量が増える。
'-Os' : 実行ファイルのサイズを小さくする。
'-ggdb' デバッガ gdb 用の情報を実行ファイルに付加する。
'-I' インクルードするヘッダが置かれている標準のディレクトリ以外の path を指定する。
'-L' リンクするライブラリが置かれている標準のディレクトリ以外の path を指定する。
'-l' リンクするライブラリを指定する。
'-Wall' 主要な警告を表示する。
'-ansi' ANSI で定められた規格を適用する。
'-std=c99' 1999 年に定められた C 言語の規格を適用する。
'-std=c++11' 2011 年に定められた C++の規格を適用する。
gcc, g++ にどのようなオプションがあるか、詳細はオンラインマニュアルで調べることができる。
$ man gcc
$ man g++gcc, g++ を使用するときは以下のオプションを指定するとよい。実行速度を改善したい場合、デバッガを使用する場合はそれぞれのオプションを追加する。
★ 練習
試してみよう。

$ gcc -Wall -ansi -std=c99 -o 2-1 2-1.c$ g++ -Wall -ansi -std=c++11 -o 2-2 2-2.cpp

2.2.1 '-o' オプション
これまでのコンパイルの指定の仕方では実行ファイルの名前はいつも a.out になる。

$ gcc 2-1.c$ ./a.out'-o' オプションを使うと実行ファイルの名前を指定できる。
$ gcc -o 2-1 2-1.c$ ./2-1

12

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0

2.2.2 '-O' オプション
以下のプログラムは sin(), cos() を組合わせて求めた値を配列の要素として代入していき、最後にその和を求める。最適化オプションを指定することで実行速度がどのように変化するか比較してみる。コマンド timeを指定することで実行にかかった時間を計測できる。
リスト 2.6: 2-6.c
12
34// gcc -Wall -ansi -std=c99 -o 2-6 2-6.c -lm#include <stdio.h>#include <stdlib.h>#include <math.h>5
6#define

SIZE 5127
8

int main(void)9{
int i, j, k;

10double a[SIZE][SIZE], sum = 0.0;

1112

for (i = 0; i < SIZE; i++) {13

for (j = 0; j < SIZE; j++) a[i][j] = 0.0;

14
15

}

16

for (i = 0; i < SIZE; i++) {for (j = 0; j < SIZE; j++) {17

for (k = 0; k < 256; k++) {18

a[i][j] = sin(i + j) + cos(i - j) - sin(a[i][j]) - cos(a[i][j]);

19

}

20

}

21
22

}

23

for (i = 0; i < SIZE; i++) {for (j = 0; j < SIZE; j++) sum += a[i][j];

24
25

}

26

printf("%f\n", sum);27

exit(EXIT_SUCCESS);

28
29

}
★ 練習
試してみよう。

$ gcc -o 2-6 2-6.c -lm$ time ./2-6$ gcc -O0 -o 2-6 2-6.c -lm$ time ./2-6

2.2. コンパイラの使い方、オプション

13

Robot Programming II Documentation, リリース 1.0$ gcc -O -o 2-6 2-6.c -lm$ time ./2-6$ gcc -O2 -o 2-6 2-6.c -lm$ time ./2-6$ gcc -O3 -o 2-6 2-6.c -lm$ time ./2-6
次に、2-6.c の最後の printf() をコメントアウトして、'-O' を指定した場合、指定しない場合の実行速度を比較してみる。

$ gcc -o 2-6 2-6.c -lm$ time ./2-6$ gcc -O -o 2-6 2-6.c -lm$ time ./2-6
printf() の有無で実行速度の変化の度合いが変わるのが見てとれるが、なぜこのような違いが現れるのだ
ろう？

2.2.3 '-Wall' オプション
次のプログラムは scanf() printf() を使った簡単な入出力のプログラムである。警告を表示する '-Wall' オプ
ションの有無でコンパイル時の表示がどのように変わるか比較してみる。
リスト 2.7: 2-7.c
1
2
3// gcc -Wall -ansi -std=c99 -o 2-7 2-7.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
int i, j;7
8
9

printf("Input number => ");10

scanf("%d", &i);11

if (i = 1) printf("%d\n", i);12exit(EXIT_SUCCESS);

13
14

}
★ 練習
試してみよう。

14

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0$ gcc -o 2-7 2-7.c$ ./2-7$ gcc -Wall -o 2-7 2-7.c$ ./2-7
文法的に間違いがあるとエラーとなり、コンパイルは中断され実行ファイルは生成されない。'-Wall' オプ
ションで表示される警告は、文法的には間違いではないが、意味のない記述、間違いや勘違いの可能性がある部分である。警告は無視しても、実行ファイルは生成されており実行もできるので、指摘された部分に問題がないことを理解していれば放置することもできる。しかし問題となる可能性がある部分なので、メッセージの内容を一通り確認しておくのがよい。

2.3 分割コンパイル、リンク
プログラムの規模が大きくなると、プログラム全体を単一のソースファイルで作成すると様々な不都合や問題が生じる。例えば数千行に及ぶようなソースファイルでは、どこに何が書いてあるか理解しておくことは難しくなる。ある機能を改良したいと思ってソースコードを見ようと思っても目的の場所までスクロールさせるのに手間がかかる。プログラムの規模が大きくなるとコンパイルの時間も比例して長くなるので、１箇所変更しただけでもその結果を確認するためのコンパイルが終わるまで長時間待たなくてはならない。
大規模なプログラムではコンパイルに数時間かかることもある。またソースファイルが単一の場合、複数人のチームで協力して開発するのが難しくなる。複数の人が並行して別々の場所を編集していると、その結果を矛盾なく統合することが難しくなる。
このようにソースファイルが長くなると生じる不都合を回避するために、分割コンパイルという手法が用いられる。分割コンパイルとは、プログラムの持つ多くの機能毎にソースファイルを分割して、それぞれを独立して開発できるようにする。
次のプログラムはいくつかの定義や関数を含むプログラムである。プログラムの動作と結果には特に意味はない。
リスト 2.8: 4-6.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-6 4-6.c#include <stdio.h>#include <stdlib.h>

4
5
6
7

#define#define#define C

A
B

100
10
(A+B)

8
9

void

f1(int a[]);

10

void

f2(int b[]);

11

void

f3(int a[], int b[], int c[]);char

name[] = "abc";

12
13(次のページに続く)

2.3. 分割コンパイル、リンク

15

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
14
15

int main(void)

16

{
int

17

a[A], b[B], c[C];18f3(a, b, c);1920

exit(EXIT_SUCCESS);

21
22

}

23
24

void

25

{
for (int i = 0; i < A; i++) a[i] = i;;26
27

f1(int a[])

}

28
29

void

30

{
for (int i = 0; i < B; i++) b[i] = i;31
32f2(int b[])

}

33
34

void

35

{

f3(int a[], int b[], int c[])int sum = 0;

36
37
38

printf("%s\n", name);39

f1(a);

40

f2(b);

41

for (int i = 0; i < A; i++) c[i] = a[i];42for (int i = 0; i < B; i++) c[i + A] = b[i];43for (int i = 0; i < C; i++) sum += c[i];44printf("%d\n", sum);45}

2.3.1 分割コンパイルの実際
上記のプログラムを複数のファイルに分割して、個々の機能を独立して変更できるようにすると次のようになる。分割コンパイルの考え方を示すために、個々のファイルは意図的に短くして、ファイル数が多くしてある。
ヘッダファイル (.h) には定義や宣言を記述する。関数のプロトタイプ宣言なども書けるが、関数の実体 (実行する内容) は書かない。複数のソースファイルで必要になるような内容をヘッダにまとめて書く。

16

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0

リスト 2.9: 4-7a.h
1
2#ifndef#defineHEADER_47A
HEADER_47A#define

A

3
4

100

5
6

#endif // HEADER_47A

リスト 2.10: 4-7b.h
1
2#ifndef HEADER_47B#defineHEADER_47B

3
4#define

B

10

5
6

#endif // HEADER_47B

リスト 2.11: 4-7c.h
12#ifndef HEADER_47C#define HEADER_47C

3
4
5#include "4-7a.h"#include "4-7b.h"

6
7#define C

(A+B)

8
9

#endif // HEADER_47C
ソースファイル (.c) には、関数の実体を書く。定義や宣言はそのファイルでだけ必要なものであればソースファイルに書いてもよい。ヘッダにすべて書くようにしてもよい。
リスト 2.12: 4-7main.c

1
2
3// gcc -Wall -ansi -std=c99 -c 4-7main.c// gcc -Wall -ansi -std=c99 -o 4-7 4-7main.o 4-7a.o 4-7b.o 4-7c.o// gcc -Wall -ansi -std=c99 -o 4-7 4-7main.c 4-7a.c 4-7b.c 4-7c.c

4
5
6
7
8
9

#include <stdio.h>#include <stdlib.h>#include "4-7a.h"#include "4-7b.h"#include "4-7c.h"

10
11

void

f3(int a[], int b[], int c[]);(次のページに続く)

2.3. 分割コンパイル、リンク

17

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
12
13

char

name[] = "abc";

14
15

int main(void)

16

{
int

17

a[A], b[B], c[C];18f3(a, b, c);1920

exit(EXIT_SUCCESS);

21
22

}

リスト 2.13: 4-7a.c
1
2// gcc -Wall -ansi -std=c99 -c 4-7a.c#include "4-7a.h"

3
4

void

5

{
for (int i = 0; i < A; i++) a[i] = i;;67f1(int a[])}リスト 2.14: 4-7b.c
1
2// gcc -Wall -ansi -std=c99 -c 4-7b.c#include "4-7b.h"

3
4

void

5

{
for (int i = 0; i < B; i++) b[i] = i;67f2(int b[])}リスト 2.15: 4-7c.c
1
2
3
4
5

// gcc -Wall -ansi -std=c99 -c 4-7c.c#include <stdio.h>#include "4-7a.h"#include "4-7b.h"#include "4-7c.h"

6
7

void

f1(int a[]);

8

void

f2(int b[]);9
10externchar name[];11(次のページに続く)

18

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
12

void

13

{

f3(int a[], int b[], int c[])int sum = 0;

14
15
16

printf("%s\n", name);17

f1(a);

18

f2(b);

19

for (int i = 0; i < A; i++) c[i] = a[i];20for (int i = 0; i < B; i++) c[i + A] = b[i];21for (int i = 0; i < C; i++) sum += c[i];22printf("%d\n", sum);23}
4-7main.c の冒頭には、コンパイルの指示が３種類示されているが、必要に応じて使い分けるとよい。最初の例では、オプションとして -c が指定されているが、これはコンパイルの結果として実行ファイルではな
く オブジェクトファイル を出力する。
★ 練習
試してみよう。

gcc -c 4-7main.c
4-7main.c からはファイル名を指定しなければオブジェクトファイルとして 4-7main.o が出力、記録される。
同様に、4-7a.c, 4-7b.c, 4-7c.c についてもオブジェクトファイルを生成できる。オブジェクトファイルに他のオブジェクトファイルやライブラリ、起動時や終了時に必要な処理を追加すると実行ファイルにすることができる。この処理を リンク という。次のコマンドでオブジェクトファイル群をリンクして実行ファイル 4-7 を生成することができる。

gcc -o 4-7 4-7main.o 4-7a.o 4-7b.o 4-7c.o
また次のコマンドではオブジェクトファイルの生成とリンクを一度に行う。この場合、オブジェクトファ
イルは処理の過程で生成されるが保存はされず、実行ファイルが生成された後は廃棄される。これまで指定していた gcc の指示は、オブジェクトファイルの生成とリンクを連続して行うように指示していたことになる。

gcc -o 4-7 4-7main.c 4-7a.c 4-7b.c 4-7c.c
ファイルを分割すると、それぞれのファイル間で依存関係が生じるので、コンパイルする時には抜けがな
いように注意する必要がある。例として、4-7a.h では A が 100 として定義されているが、何らかの理由で200 に変更したとする。その場合、ヘッダファイルを書き換えただけでは不十分で、4-7a.h をインクルード
しているソースファイルをすべて再コンパイルし直さないと変更が反映されない。一部のソースファイル
をコンパイルし忘れると、ある部分の処理では A = 100 として扱われ、別の処理では A = 200 となり、不整合が生じることになる。
ソースファイル、ヘッダファイルを分割しても、次のようにすべてインクルードするように書くのはよくない。分割したソースファイルをすべてインクルードしているので、これでは単一のファイルにすべて書

2.3. 分割コンパイル、リンク

19

Robot Programming II Documentation, リリース 1.0
いたのと同じである。ファイル毎に独立したオブジェクトファイルは生成されない。どこか１箇所でも変更したら、プログラム全体、すべての行を再コンパイルする必要がある。
リスト 2.16: 4-8.c
1
2
3
4
5
6

// gcc -Wall -ansi -std=c99 -o 4-8 4-8.c#include <stdio.h>#include <stdlib.h>#include "4-7a.h"#include "4-7b.h"#include "4-7c.h"

7
8
9
10

#include "4-7a.c"#include "4-7b.c"#include "4-7c.c"

11
12

int main(void)

13

{
int

14

a[A], b[B], c[C];15f3(a, b, c);1617exit(EXIT_SUCCESS);

18
19

}

2.3.2 分割コンパイルを可能にするための条件
分割コンパイルを行う際には次の事柄を考える必要がある。

• ファイルを分割するときには、何らかの関数、または関連する関数をまとめた機能を単位として (機能モジュール) 分けるのが一般的であるが、どのように機能を分けるか。

• その機能モジュールへの入力は何か、出力は何か。
• その機能モジュールが必要とする定義や宣言は何か。
• その機能モジュールが必要とするモジュール外部の関数やモジュールは何か。
これらの事柄が明確になると、他の部分とは独立してモジュールのプログラム作成が可能となり、それぞれのモジュールを並行して開発することができる。

20

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0

2.4 ヘッダに書く内容、二重定義を防ぐ
ヘッダには次の要素を書く。

• インクルードするヘッダ• #deﬁne による名前や定数の定義• グローバル変数の定義• 関数のプロトタイプ宣言
上記の要素で、単一のソースファイルでのみ必要とされるものについてはそのファイルに直接書いてもよ
いが、そのファイル用の専用のヘッダファイルにしてもよい。複数のソースファイルで必要とされる要素
は、ヘッダファイルで 1 回のみ、1 箇所で定義して複数のファイルでインクルードする。これにより内容を変更する場合は 1 箇所だけの書き換えでよくなり、保守性が高まり、間違える可能性が低くなる。
ヘッダファイルを複数回インクルードして、定義や宣言が複数回行われると文法エラーや間違いのもととなる。よってヘッダファイルを記述する場合は、複数回インクルードされてもその内容が反映されるのは1 回だけとなるよう、次のような記述をする。
リスト 2.17: 4-7a.h
1
2#ifndef#defineHEADER_47A
HEADER_47A#define

A

3
4

100

5
6

#endif // HEADER_47A
1, 2, 6 行目が二重定義を防ぐ仕組みになっている。1 行目は「もし HEADER_47A が定義されていなければ (以降を有効にする) 」、2 行目は HEADER_47A を定義する、6 行目は 1 行目からの処理をここで終える、という意味になる。もともとこのヘッダでしたかったのは 4 行目の定義だけである。二重定義を防ぐ仕組
みにより、4 行目はこのヘッダが最初にインクルードされたときのみ有効になる。2 回目以降のインクルードでは 1 回目のインクルードで既に HEADER_47A が定義されているので 4 行目はスキップされる。
この方法のポイントは、HEADER_47A のように各ヘッダファイル毎に固有の文字列を使用することである。複数のヘッダファイルで同じ文字列を使ってしまうと、二重定義を防ぐ仕組みが機能しなくなる。

2.5 分割コンパイルを支援するツール make
ヘッダファイルやソースファイルの数が多くなると、それぞれのファイル間の依存関係を人が管理するのが難しくなり、見落としや勘違いが生じる。そのような間違いを防ぐために make という分割コンパイルを行う時の開発支援ツールがある。make では、専用の書式でファイル間の依存関係を事前に記述しておくだけで、コンパイル時にはそれらのルールに従って、必要なファイルだけ検出してコンパイルしてくれる。
次に示すのは、先に示した 4-7*.c 群の依存関係を記述したファイルである。

2.4. ヘッダに書く内容、二重定義を防ぐ

21

Robot Programming II Documentation, リリース 1.0

リスト 2.18: Makeﬁle (Standard)
1

4-7: 4-7main.o 4-7a.o 4-7b.o 4-7c.ogcc -o 4-7 4-7main.o 4-7a.o 4-7b.o 4-7c.o2
3
4

4-7main.o: 4-7main.c 4-7a.h 4-7b.h 4-7c.hgcc -c 4-7main.c

5
6
7

4-7a.o: 4-7a.c 4-7a.h

8gcc -c 4-7a.c

910

4-7b.o: 4-7b.c 4-7b.h

11gcc -c 4-7b.c

12
13

4-7c.o: 4-7c.c 4-7a.h 4-7b.h 4-7c.hgcc -c 4-7c.c14

ここに記述されているのは、２行ずつが単位となっていて、ターゲットを生成するにはどのファイルが必要とされて、コマンドとして何を実行すればよいかが記述されている。ファイル名は Makeﬁle とするのが一般的で、この名前にすればファイル名の指定を省略できる。
ターゲットの生成ルールの一般形は次のようになる。コマンド行を指定する行は先頭に TAB 文字を挿入する必要がある。
生成するファイル: 生成するときに必要となるファイル群
生成するためのコマンド行

Makeﬁle を作成した後で make を実行する時は次のように指定する。
★ 練習
試してみよう。

$ makeMakrﬁle 中の記述は先頭から評価、実行されていくので最終的に生成したいターゲットを先に書く。その後、先に指定したターゲットを構成するオブジェクトファイルなどを順に指定する。

make はコンパイルに関する基本的な依存関係、ルールを内蔵しているので、その仕組みを利用すると、Makeﬁle の記述を簡潔にできる。例として、ターゲットが a.o となっている場合は、ソースファイルは a.cがもとになっていると仮定して処理を進める。またその生成ルールは cc -c a.c であるということも組み込まれている。上記の Makeﬁle は以下のように書いても同じように動作する。
リスト 2.19: Makeﬁle (Simple version)
1

4-7: 4-7main.o 4-7a.o 4-7b.o 4-7c.ogcc -o 4-7 4-7main.o 4-7a.o 4-7b.o 4-7c.o23(次のページに続く)

22

第 2 章 コンパイル、実行ファイルの生成

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
4

4-7main.o: 4-7a.h 4-7b.h 4-7c.h

5

4-7a.o: 4-7a.h

64-7b.o: 4-7b.h

74-7c.o: 4-7a.h 4-7b.h 4-7c.h

2.6 まとめ
この章の目標• C 言語、C++ それぞれのコンパイラの基本的な使い方がわかる。
• ヘッダとライブラリの指定の仕方がわかる。
• オンラインマニュアルの使い方がわかる。
• 主要なオプションの機能を理解して適切なものを指定できる。
• 分割コンパイル、リンクの考え方、仕組みを理解する• ヘッダに書く内容にどのようなものがあるか理解する• make の仕組み、使い方を理解して活用できる
練習問題

1. /usr/include にどのようなヘッダファイルがあるか調べる。Ubuntu 18.04 で調べたところ、381 個のファイルがあった。レポートには個々のファイルの内容を含める必要はなく、ファイル名の一部を示せばよい。(e2-12345678.txt)

2. /lib/x86_64-linux-gnu/ にどのようなライブラリファイルがあるか調べる。Ubuntu 18.04 で調べたとこ
ろ、190 個のファイルがあった。レポートではファイル名の一部を示せばよい。

3. /usr/include/math.h の中で円周率 π (M_PI) の値がどう定義されているか調べる。なお Ubuntu の versionの変更によりヘッダの内容が変更された。Ubuntu 18.04 以前は 770 行付近、Ubuntu 20.04 以降は 1070
行付近に記述がある。

4. /usr/include/math.h の中で自然対数の底 e (M_E) の値がどう定義されているか調べる。
5. /usr/include/stdlib.h の中で EXIT_SUCCESS の定義されている場所を調べる (90 行付近)。
6. 自然対数の関数 log() の仕様、必要なヘッダ、ライブラリをオンラインマニュアルで調べる。
7. 指数関数 exp() の仕様、必要なヘッダ、ライブラリをオンラインマニュアルで調べる。
8. 2-6.c で最後の printf() をコメントアウトしたときの実行速度の変化の理由を考える。
9. 分割コンパイルを活用するとどのようなメリットがあるか説明しなさい。(e2-91011.txt)
10. リンクとは何をすることか説明しなさい。
11. make を使うとどのようなメリットがあるか説明しなさい。

2.6. まとめ

23

25

第 3 章 デバッグの方法、デバッガの使い方

プログラムは「作る」能力だけではなく、「間違いを見つける」、「直す」能力も同じように必要とされる。
プログラムに含まれる間違いを バグ と言い、その間違いを修正する作業を デバッグ という。
正しく動かないプログラムは、最後は自分の力で間違いを見つけて修正する必要がある。間違いがあるときに、いつも都合よく誰かに間違いを見つけてもらったり、直してもらえるわけではないので、自力で対処できるように訓練する必要がある。
プログラムを作成する過程では、様々な書き間違い、文法的な間違い、勘違い、文法的には正しいが結果が正しくないロジックの間違い、などで正しく動作するプログラムを完成させるまでには何度も修正と動作確認を行うことがよくある。この章では文法エラーがある場合と、ない場合のそれぞれでどのようにプ
ログラムを直すか、その考え方を理解して実践できるようになることを目指す。

3.1 文法エラーがある場合のデバッグ
3.1.1 C 言語 文法エラーがある場合
文法エラーがある場合は、コンパイルの途中でエラーメッセージが表示されコンパイルは中断される。エラーメッセージには通常プログラムのどこ（何行目）に間違いがあるか示されるので、その内容をもとに間違いを見つけて修正する。エラーメッセージが数多く表示される場合は、先に表示されるものから順に対処する。エラーが非常に多く見える場合でも、運が良ければ最初のエラーを直したところでエラーがなくなることもある。
以下に示すプログラムはそれぞれどこかに間違いがあり、コンパイルできない。プログラムを作成していると、それぞれ日常的によく見かける間違いであるので、間違いの箇所を特定して正しく動くように修正する。
一般的に他人が作成したプログラムを理解して修正することは難しい作業とされているが、以下のプログ
ラムは数行の短いプログラムであるので、挑戦してみよう。次の示す順番で直すことを試みる。

1. ソースファイルを見るだけで間違いの場所を推定する。
2. コンパイルしてみて、エラーメッセージを参考にして間違いを見つける。
3. あらゆる手段を講じて間違いを見つけて修正する。
★ 練習
それぞれのプログラムの間違いを上記の手順で見つけてみよう。

Robot Programming II Documentation, リリース 1.0

リスト 3.1: 3-1.c
12
3// gcc -Wall -ansi -std=c99 -o 3-1 3-1.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
printf("Hello world.\n")78exit(EXIT_SUCCESS);

9
10

}

リスト 3.2: 3-2.c
12
3// gcc -Wall -ansi -std=c99 -o 3-2 3-2.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
qrintf("Hello world.\n");7
8

exit(EXIT_SUCCESS);

9
10

}

リスト 3.3: 3-3.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-3 3-3.c#include <stdi0.h>#include <std1ib.h>

4
5

int main(void)6{
printf("Hello world.\n");7
8

exit(EXIT_SUCCESS);

9
10

}

リスト 3.4: 3-4.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-4 3-4.c#include <stdio.h>#include <stdlib.h>4(次のページに続く)

26

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0(前のページからの続き)
5

int main(void)6{
printf("Hello world.\n");7
8

exit(EXlT_SUCCE55);

9
10

}

リスト 3.5: 3-5.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-5 3-5.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
printf("Hello world.\n);78exit(EXIT_SUCCESS);

9
10

}

リスト 3.6: 3-6.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-6 3-6.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
printf("Hello world.\n");7// １行コメントはこのように書く

8exit(EXIT_SUCCESS);

9
10

}

リスト 3.7: 3-7.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-7 3-7.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)

6

{

7

int a;

8
9

printf("Input number -> ");10

scanf("%d", a);(次のページに続く)

3.1. 文法エラーがある場合のデバッグ

27

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

printf("%d\n", a);1112exit(EXIT_SUCCESS);

13
14

}
コンパイル時のエラーメッセージはそれぞれ以下のようになる。これらのメッセージから間違いの箇所を特定してみる。
★ 練習
エラーメッセージの内容を読んで間違いを見つけてみよう。

$ gcc -o 3-1 3-1.c
3-1.c: In function main :3-1.c:10:5: error: expected;

beforeexit

exit(EXIT_SUCCESS);^~~~$ gcc -o 3-2 3-2.c
3-2.c: In function

main

3-2.c:8:5: warning: implicit declaration of function

qrintf ; did you mean

printf[-Wimplicit-function-declaration]
qrintf("Hello world.\n");^~~~~~
printf/tmp/ccRDv9O7.o: 関数 `main' 内:
3-2.c:(.text+0x11): `qrintf' に対する定義されていない参照です
collect2: error: ld returned 1 exit status$ gcc -o 3-3 3-3.c
3-3.c:3:10: fatal error: stdi0.h: そのようなファイルやディレクトリはありません#include <stdi0.h>^~~~~~~~~
compilation terminated.

$ gcc -o 3-4 3-4.c
3-4.c: In function

main :

3-4.c:10:10: error:

EXlT_SUCCE55

mean

,→

undeclared (first use in this function); did you␣

EXIT_SUCCESS ?

exit(EXlT_SUCCE55);^~~~~~~~~~~~
EXIT_SUCCESS
3-4.c:10:10: note: each undeclared identifier is reported only once for each function␣
it appears in,→28

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0$ gcc -o 3-5 3-5.c
3-5.c: In function

main :

3-5.c:8:12: warning: missing terminating " character
printf("Hello world.\n);^3-5.c:8:12: error: missing terminating " character
printf("Hello world.\n);^~~~~~~~~~~~~~~~~
3-5.c:10:23: error: expected)

before;tokenexit(EXIT_SUCCESS);^
3-5.c:10:5: error: invalid use of void expressionexit(EXIT_SUCCESS);^~~~
3-5.c:11:1: error: expected;

before

}

token

}
^

$ gcc -o 3-6 3-6.c
3-6.c: In function

main :

3-6.c:9:1: error: stray\343

in program\200

in program\200

in program^
3-6.c:9:2: error: stray^3-6.c:9:3: error: stray^$ gcc -o 3-7 3-7.c
3-7.c: In function

main :

3-7.c:11:13: warning: format
has type

,→

int

%d

expects argument of type

int * , but argument 2␣

[-Wformat=]scanf("%d", a);~^3.1. 文法エラーがある場合のデバッグ

29

Robot Programming II Documentation, リリース 1.0

3.1.2 C++言語 文法エラーがある場合
C++では同じプログラムでもエラーの出方が異なる。間違いの箇所は C 言語の例と同じであるので、エラー
メッセージの内容を比較してみる。
リスト 3.8: 3-11.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-11 3-11.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
printf("Hello world.\n")78exit(EXIT_SUCCESS);

9
10

}

リスト 3.9: 3-12.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-12 3-12.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
qrintf("Hello world.\n");7
8

exit(EXIT_SUCCESS);

9
10

}

リスト 3.10: 3-13.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-13 3-13.cpp#include <cstdi0>#include <cstd1ib>

4
5

int main(void)6{
printf("Hello world.\n");7
8

exit(EXIT_SUCCESS);

9
10

}

30

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0

リスト 3.11: 3-14.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-14 3-14.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
printf("Hello world.\n");7
8

exit(EXlT_SUCCE55);

9
10

}

リスト 3.12: 3-15.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-15 3-15.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
printf("Hello world.\n);78exit(EXIT_SUCCESS);

9
10

}

リスト 3.13: 3-16.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-16 3-16.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
printf("Hello world.\n");7// １行コメントはこのように書く

8exit(EXIT_SUCCESS);

9
10

}

リスト 3.14: 3-17.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 3-17 3-17.c#include <cstdio>#include <cstdlib>

4(次のページに続く)

3.1. 文法エラーがある場合のデバッグ

31

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
5

int main(void)6{
int a;

7
8
9

printf("Input number -> ");10

scanf("%d", a);11

printf("%d\n", a);12exit(EXIT_SUCCESS);

13
14

}
g++ によるコンパイル時のエラーメッセージはそれぞれ以下のようになる。
$ g++ -o 3-11 3-11.cpp
3-11.cpp: In function

int main() :3-11.cpp:10:5: error: expected;

beforeexit

exit(EXIT_SUCCESS);^~~~$ g++ -o 3-12 3-12.cpp
3-12.cpp: In function

int main() :3-12.cpp:8:5: error:

qrintfwas not declared in this scope

qrintf("Hello world.\n");^~~~~~
3-12.cpp:8:5: note: suggested alternative:

printfqrintf("Hello world.\n");^~~~~~
printf$ g++ -o 3-13 3-13.cpp
3-13.cpp:3:10: fatal error: cstdi0: そのようなファイルやディレクトリはありません#include <cstdi0>^~~~~~~~
compilation terminated.

$ g++ -o 3-14 3-14.cpp
3-14.cpp: In function

int main() :3-14.cpp:10:10: error:

EXlT_SUCCE55

was not declared in this scopeexit(EXlT_SUCCE55);^~~~~~~~~~~~
3-14.cpp:10:10: note: suggested alternative:

EXIT_SUCCESS

exit(EXlT_SUCCE55);^~~~~~~~~~~~
EXIT_SUCCESS

32

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0$ g++ -o 3-15 3-15.cpp
3-15.cpp:8:12: warning: missing terminating " character
printf("Hello world.\n);^3-15.cpp:8:12: error: missing terminating " character
printf("Hello world.\n);^~~~~~~~~~~~~~~~~
3-15.cpp: In function

int main() :3-15.cpp:10:23: error: expected)

before;tokenexit(EXIT_SUCCESS);^$ g++ -o 3-16 3-16.cpp
3-16.cpp:9:1: error: stray\343

in program\200

in program\200

in program^
3-16.cpp:9:2: error: stray^3-16.cpp:9:3: error: stray^$ g++ -o 3-17 3-17.cpp
3-17.cpp: In function

int main() :3-17.cpp:11:18: warning: format
2 has type

,→

int

%d

expects argument of typeint* , but argument␣[-Wformat=]scanf("%d", a);^

3.2 文法エラーがない場合のデバッグ
文法エラーがない場合はコンパイル時にエラーが表示されずに終了することが多い。ただし現在のコンパ
イラは、文法エラーでない場合でも間違いの可能性があるときは警告を表示することがある。よって警告
表示のオプションを指定しておくと警告が表示され間違いに気がつくことがある。以下のプログラムはい
ずれも文法的には間違いはないが正しい結果が得られず、修正すべき部分がある。
このような間違いには• 文法の勘違いだがたまたまエラーとならない• 単純な書き間違い• ロジックの間違い、勘違いなどがある。

3.2. 文法エラーがない場合のデバッグ

33

Robot Programming II Documentation, リリース 1.0★ 練習
次に示すプログラムでそれぞれどこに問題があるか考えてみよう、そして正しく動くように直してみよう。
リスト 3.15: 3-21.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-21 3-21.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
int a, b;7
8
9

printf("Input number -> ");10

scanf("%d", &a);

11

printf("Input number -> ");12

scanf("%d", &b);

13
14

if (a = b) printf("a and b are equal.\n");

15

else

printf("a and b are not equal.\n");16

exit(EXIT_SUCCESS);

17
18

}

リスト 3.16: 3-22.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-22 3-22.c#include <stdio.h>#include <stdlib.h>4
5#define

NUM

10

6
7

int main(void)

8

{

9

int i, sum;10

int a[NUM] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};11

for (i = 1; i <= NUM; i++); {12

sum += a[i];

13
14

}

15

printf("Average is %f\n", (float)sum / NUM);

16

exit(EXIT_SUCCESS);

17
18

}
デバッグの具体的な方法として次の２つがあげられる。

34

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0• プログラム内の所々に printf() を挿入して、変数の値を確認することで間違いを推定する。
• デバッグの作業をサポートする デバッガ という専用のプログラムがあるので、それを活用する。
プログラムの間違いの多くは、処理結果が記録される変数の値が正しい値や想定した本来なるべき値と異なることで顕在化する。よって、プログラムの様々な場所で変数の値を確認することで、どこに間違いがあるか、どのような間違いかを推測することができる。

3.3 printf() を使うデバッグ
上記のプログラムに printf() を挿入したのが次のプログラムである。挿入した行の上には目印として///////////////////// を付加している。
★ 練習
表示される変数の値に基づいてどこが間違っているのか考えてみよう。
リスト 3.17: 3-21b.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-21b 3-21b.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)6{
int a, b;7
8

10

printf("Input number -> ");scanf("%d", &a);

11

printf("Input number -> ");12

scanf("%d", &b);

9

13
14
15

/////////////////////////////////////////////
printf("a=%d b=%d\n", a, b);16
17

if (a = b) printf("a and b are equal.\n");

18

else

printf("a and b are not equal.\n");

19
20
21

/////////////////////////////////////////////
printf("a=%d b=%d\n", a, b);22

exit(EXIT_SUCCESS);

23
24

}

3.3. printf() を使うデバッグ

35

Robot Programming II Documentation, リリース 1.0

リスト 3.18: 3-22b.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-22b 3-22b.c#include <stdio.h>#include <stdlib.h>4
5#define

NUM

10

6
7

int main(void)

8

{

9

int i, sum;10

int a[NUM] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};11

for (i = 1; i <= NUM; i++); {

12
13
14
15
16
17
18

////////////////////////////////////////////
printf("sum=%d i=%d a[i]=%d\n", sum, i, a[i]);sum += a[i];}////////////////////////////////////////////
printf("sum=%d\n", sum);

19

printf("Average is %f\n", (float)sum / NUM);

20
21

exit(EXIT_SUCCESS);

22
23

}
printf() を使う方法は特別な知識を必要とせずすぐに実行できるが、規模の大きなプログラムになるとどこを調べるとよいのか、それを決めることも簡単ではない。例えば最近の OS のソースファイルの総行数は以下のようになる。

• 最近の Linux kernel : 2000 万行超• Debian 5.0 : 3 億 2000 万行超• Windows 7 : 1 億行
このような大規模なソフトウェアでは、ツールを使わずに手作業でデバッグを行うのはほぼ不可能である。
規模の大きなプログラムでは次に示すデバッガを活用するほうが早く効率的に問題を解決できる。

36

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0

3.4 デバッグ用のツール「デバッガ」を使うデバッグ
gcc, g++ と同じように GNU プロジェクトにより開発、公開されているデバッガとして gdb がある。ここでは gdb を使ったデバッグの基礎を取り上げる。次に示すプログラムはコンパイルは問題なく終了するが、実行するとメッセージを表示して異常終了してしまう。
リスト 3.19: 3-23.c
1
2
3// gcc -Wall -ansi -std=c99 -ggdb -o 3-23 3-23.c#include <stdio.h>#include <stdlib.h>4
5
6#define#define

NUM1
NUM2

100
50

7
8

int main(void)

9

{

10

int i, sum = 0;

11

int a[NUM1+NUM2], b[NUM1+NUM2], c[NUM1+NUM2], d[NUM1+NUM2], e[NUM1+NUM2];

12

for (i = 0; i < NUM1+NUM2; i++) {13

a[i] = i * 10;

14
15

}

16

for (i = 0; i < NUM1+NUM2; i++) {b[i] = a[i] + 10;

17
18

}

19

for (i = 0; i < NUM1+NUM2; i++) {c[i] = b[i] / 20;

20
21

}

22

for (i = 0; i < NUM1+NUM2; i++) {d[i] = c[i] - 10;

23
24

}

25

for (i = 0; i < NUM1+NUM2; i++) {e[i] = d[i] * 10;

26

}

27
28

for (i = 0; i < NUM1*NUM2; i++) {29

sum += a[i] + b[i] + c[i] + d[i] + e[i];

30
31

}

32

printf("Sum is %d\n", sum);33

exit(EXIT_SUCCESS);

34
35

}
★ 練習
この後の手順を辿って実際に実行してみて、デバッガの使い方を理解しよう。

3.4. デバッグ用のツール「デバッガ」を使うデバッグ

37

Robot Programming II Documentation, リリース 1.0$ gcc -o 3-23 3-23.c$ ./3-23
Segmentation fault (コアダンプ)
このプログラムを gdb を使って間違いを見つけて修正する。gdb を使うときにはデバッグ用の情報を付加した実行プログラムが必要になるため、'-ggdb' オプションを追加してコンパイルする。

$ gcc -ggdb -o 3-23 3-23.c
gdb を使うときにはコマンドライン引数として実行ファイルを指定する。メッセージが表示され起動すると、プロンプト (gdb) を表示して入力待ちとなる。

$ gdb ./3-23
GNU gdb (Ubuntu 8.1-0ubuntu3.1) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Type "show copying"and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./3-23...done.
(gdb)
r コマンド (run の意味) を指定して、デバッグ対象のプログラムを gdb の下で実行する。何も問題がなければ正常終了して入力待ちに戻る。間違いがあって異常終了したときには、どこで終了したか、終了したときの状態を表示して入力待ちに戻る。この場合は、signal SIGSEGV を受信して、Segmentation fault のエラーで異常終了したことが示されている。その下には 3-23.c 中の 31 行目に原因があることが示されている。

(gdb) rStarting program: /home/any/Lecture/rp2/3-23
Program received signal SIGSEGV, Segmentation fault.
0x000055555555485b in main () at 3-23.c:31
31

sum += a[i] + b[i] + c[i] + d[i] + e[i];以下では gdb の代表的なコマンドの使い方を示す。前の画面で原因の箇所は表示されているが、w コマン
ド (where の意味) でも原因の箇所を表示させることができる。

(gdb) w#038

0x000055555555485b in main () at 3-23.c:31

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0
l コマンド (list の意味) を使うと、原因の箇所周辺のソースリストを表示させることができる。
(gdb) l26

for (i = 0; i < NUM1+NUM2; i++) {27
28

e[i] = d[i] * 10;
}

29
30

for (i = 0; i < NUM1*NUM2; i++) {31

sum += a[i] + b[i] + c[i] + d[i] + e[i];

32

}

33

printf("Sum is %d \n", sum);34
35

exit(EXIT_SUCCESS);

p コマンド (print の意味) を使うと終了した時の各変数の値を表示させることができる。変数の指定はソー
スリスト中で書いていたそのままの表記で指定できる。gdb の実行を中断、終了するときは q コマンド (quitの意味) を指定する。

(gdb) p sum$2 = 315290160(gdb) p a[i]$3 = 708457779(gdb) p b[i]$4 = 1230197573(gdb) p i
$5 = 1392(gdb) q
ここまで表示を見てみると、変数 i の値が本来の値よりも大きくなっていることに気がつくかもしれない。
配列はそれぞれ a[NUM1+NUM2] のように NUM1+NUM2=150 個分しか領域を確保していないが、i = 1302はそれを明らかに超過していることがわかる。これをもとに 31 行周辺を確認すると、30 行の for 文の終了
条件が本来は NUM1+NUM2 であるべきところが、書き間違いで NUM1*NUM2 となっていて、それが原因で i の値が大きくなり、配列として確保した領域の外側をアクセスしていたことがわかる。

Segmentation fault はこの例のように、配列のアクセスに問題がある場合に表示されるので、間違いとエラー
メッセージの典型的な組み合わせとして覚えておくとよい。
別の使い方として、プログラムが終了するまで一気に実行するのではなくて、少しずつ実行してその時々の変数の内容を確認できる。実行の単位は、ソースファイルを見ながら必要な点に break point という区切りを設定して、実行中に break point に達する度に中断して変数を確認できる。次に break point の使い方を見てみよう。

b コマンド (break point の意味) は、次に示すようにソースファイルの何行目に設定するかを指定する。その後、r コマンドを指定すると実行が始まり、設定した break point の位置で一旦止まる。

3.4. デバッグ用のツール「デバッガ」を使うデバッグ

39

Robot Programming II Documentation, リリース 1.0(gdb) b 3-23.c:31
Breakpoint 1 at 0x811: file 3-23.c, line 31.
(gdb) rStarting program: /home/any/Sphinx/rp2/rp2src/3-23
Breakpoint 1, main () at 3-23.c:31
31

sum += a[i] + b[i] + c[i] + d[i] + e[i];ここで変数 i の値を表示させてみると繰り返しの１回めであるので、初期値の 0 になっていることが確認できる。実行を再開するには c コマンド (continue の意味) を指定する。

(gdb) p i
$1 = 0(gdb) cContinuing.
Breakpoint 1, main () at 3-23.c:31
31

sum += a[i] + b[i] + c[i] + d[i] + e[i];再び break point の位置で止まる。繰り返しの 2 回めであるので、i = 1 になっている。以後、しばらく実行
して最後に i, a[i] を表示した。

(gdb) p i
$2 = 1(gdb) cContinuing.
中略(gdb) cContinuing.
Breakpoint 1, main () at 3-23.c:31
31

sum += a[i] + b[i] + c[i] + d[i] + e[i];(gdb) p i
$4 = 3(gdb) p a[i]$5 = 30
このように break point の機能を使うと、間違いの有無に関わらず、実行中の変数の変化を追跡できる。この例では break point を１個だけ設定したが、必要であれば 2 個以上設定することができる。

40

第 3 章 デバッグの方法、デバッガの使い方Robot Programming II Documentation, リリース 1.0
gdb には、ここで示した以外にも豊富な機能がある。gdb のその他の機能はオンラインマニュアルで確認できる。

3.5 まとめ
この章の目標• コンパイル時のエラーメッセージや警告メッセージからプログラムの間違いの箇所を見つけられる。
• よくある間違いに対応するメッセージのパターンを理解して、間違いの箇所を早く見つけられる。
• デバッグの方法を理解して実践できる。
• デバッガの使い方を理解して、プログラムの修正に活用できる。
練習問題

1. 3-1.c の間違いを説明しなさい。(e3-123456789.txt)
2. 3-2.c の間違いを説明しなさい。
3. 3-3.c の間違いを説明しなさい。
4. 3-4.c の間違いを説明しなさい。
5. 3-5.c の間違いを説明しなさい。
6. 3-6.c の間違いを説明しなさい。
7. 3-7.c の間違いを説明しなさい。
8. 3-21.c の間違いを説明しなさい。正しく動くように直しなさい。
9. 3-22.c の間違いを説明しなさい。正しく動くように直しなさい。
10. デバッガを使って、3-21.c, 3-22.c の実行中の変数の値を調べて、その結果に基づいてプログラムの間違いを特定しなさい。(e3-10.txt)
11. 3-24.c は自然対数の底 e を漸化式

1/0! + 1/1! + 1/2! + 1/3! + ... + 1/n! により求めるプログラムであるが、計算結果が無限大に発散してしまい正しく動作しない。デバッガを使って間違いを見つけて修
正しなさい。(e3-11.txt)
リスト 3.20: 3-24.c
1
2
3// gcc -Wall -ansi -std=c99 -o 3-24 3-24.c#include<stdio.h>#include<stdlib.h>

4
5

int

6

{

main(void)

7

int

8

float

i, n, m = 1;
sum = 0.0;9

(次のページに続く)

3.5. まとめ41

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
10

printf("Input n => ");11

scanf("%d", &n);

12for (i = 0; i <= n; i++) {13

m *= i;14

sum += 1 / (float)m;

15
16

}

17

printf("%f\n", sum);// i を 0 から n まで回す// i! (i の階乗) を求める// 1/i! を合計 sum に加える

18

exit(EXIT_SUCCESS);

19
20

}

42

第 3 章 デバッグの方法、デバッガの使い方

43

第 4 章 プログラム作成の基本スキル

この章では、C 言語、C++ に共通するプログラム作成時に実践すると有効と思われる基本スキルを取り上げる。いずれも小さなプログラムを作成するときには無視しても大して問題にはならない。規模の大きなプログラムを作るときには少しでも間違いを減らすため、また保守性を高めて後日プログラムを見たときに容易に内容が理解できて、必要に応じて修正や機能拡張ができるようにするための知恵、手法である。

4.1 プログラムを作る能力とはプログラムを作れるようになるには、• 使うプログラミング言語の文法や記述法、機能に関する 知識• 必要とする機能を実現するために、 命令を組み合わせる能力が必要となり、どちらが欠けても作ることはできない。
文法の知識は勉強して理解、覚えてしまえばそれで足りるが、命令を組み合わせる能力は覚えて終わるものではないので、数多くの練習、経験を経て身に付ける必要がある。
これはスポーツのいずれかの種目を習得するのに似ている。例として、水泳のクロールで泳げるようにな
るには、手足の動かし方、呼吸の仕方を知識として知っているだけでは不十分で、繰り返し練習して上手に泳げるようになる必要がある。
同様にプログラミングの学習では、文法を一通り勉強して覚えるだけでは不十分で、並行してプログラム
を作る練習を数多く行って、自分が作ったプログラムと正しいプログラムを比較して、違いがあれば命令の組み合わせの正しい考え方を身につけていく必要がある。

4.2 プログラムの内容を説明するドキュメントの重要性
少し複雑な処理を行うプログラムになると、理解を容易にするためのコメント等がきちんと書かれていな
い場合には、たとえ自分が作成したプログラムであってもすぐには内容を思い出せないことがある。作ってから 3 ヶ月後の自分は他人と同じで、なぜそのようなプログラムにしたのか理解できず、他人のプログ
ラムを読むのと同様に最初から注意深く読まないとわからないことが実際にある。
そのような事態を防ぐためにも変数名や関数名に関するコメント、説明のドキュメントなどをプログラムの詳細を覚えているうちに早い段階で作成しておくことが必要になる。100 行を超えるような長いプログ
ラムや、短くても処理の内容が複雑ですぐには理解できないプログラムでは、ドキュメントが特に重要になる。ここでドキュメントと書くと分量が多い詳細な文書をイメージするかも知れないが、そこまで本格的なものでなくてもプログラムの重要な箇所に若干のコメントを書き加えるだけでもその効果は大きい。

Robot Programming II Documentation, リリース 1.0• 作った本人には当たり前のことでも、書き表しておかないと他人にはわからないことはいくらでもある。3 ヶ月後には作った本人も忘れる。

• プログラムを作った時に何を考えたか、プログラムの表面的な記述には表れない「もとになった考え、意図」を記録する。

• 他人がプログラムを見た時にわかり難いところはどこか。
• 他人がプログラムを使うときや直す時に勘違いしやすい、間違えやすいところ「罠、落とし穴」を想像して明記する。

• 関数やプログラムの全体像がわかるような一通りの処理の流れを書く。
• 1 行ずつの細かい動作ではなく、ひとまとまりの意味のある処理のブロック毎に説明を書く。
• 定数や変数は何を表すのか、なぜその値なのか、また長さや時間の単位があれば明示する。
• 曖昧な書き方をせず、明確に書く。
• 言葉や表現を考えて誤解される可能性を低くする。
次の 2 つのプログラムは同じプログラムに、異なるコメントを付した例である。
★ 練習
第三者がプログラムを理解する際に、どちらのコメントがより有用か考えてみよう。その違いは何か。
リスト 4.1: 4-1a.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-1a 4-1a.c#include <stdio.h>#include <stdlib.h>4
5#defineNUM

12// NUM を 12 とする

6
7

int main(void)

8

{

9

int i, sum; // 変数は i, sum の 2 個を使用する

10

int a[NUM] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // NUM 個分の配列 a[]11

for (i = 0; i < NUM; i++); { // 配列の個数分繰り返す

12

sum += a[i]; // sum に i

13番目の a[] を足す}
printf("Average is %f\n", (float)sum / NUM); // 合計値を個数で割る

14
15
16

exit(EXIT_SUCCESS);

17
18

}

リスト 4.2: 4-1b.c
1
2// gcc -Wall -ansi -std=c99 -o 4-1b 4-1b.c#include <stdio.h>(次のページに続く)

44

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
3#include <stdlib.h>4
5#defineNUM

12 // 配列のサイズは 12, 月毎の値を扱う

6
7
8
9
10
11

// 配列の要素の平均値を求めるプログラム
int main(void){
int i, sum; // sum は合計値を入れる
int a[NUM] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};12

for (i = 0; i < NUM; i++); {13

sum += a[i]; // 配列の各要素の和を sum に求める

14
15

}

16

printf("Average is %f\n", (float)sum / NUM); // 平均値

17exit(EXIT_SUCCESS);

18
19

}

4.3 関数名、変数名には意味を持たせる
規模の大きなプログラムを作るときには、変数名や付随するコメントを見たときに、すぐにどういう働きをする変数や関数か理解できるような名前やコメントを付ける。特にプログラムの実行中ずっと存在する
変数、プログラムの動作を決める変数などはわかりやすい名前、コメントを付ける。一方で、使い捨ての変数、局所的に短期間しか存在しない変数は簡単な名前でも問題ないことが多い。
関数名、変数名の決め方では、以下に示す方法が使われることがある。すべてを使う必要はないが、自分が良いと考えるものを取り入れるとよい。

• 重要な変数や関数には長い詳しい名前をつける。
• 一時的にしか使わない、存在しない、重要でない変数や関数には短い名前をつける。
• 変数名の最初は名詞、関数名の最初は動詞にする。
• 内容がわかるように複数の言葉を組み合わせる。
• 複数の単語の間をアンダーバー (_) で繋ぐ。
• 複数の単語を並べる際に、単語の最初の文字を大文字、残りを小文字にする。
• 変数名の最初に属性が分かる文字を付加する。例として、グローバル変数には 'g', クラスのメンバ変数 (C++の機能) には 'c', ポインタには 'p', など。

• 抽象的な意味の単語を使わず、具体的な意味の単語を使う。
• 様々な解釈が可能な単語を使わず、誤解されない明確な意味の単語を使う。

4.3. 関数名、変数名には意味を持たせる

45

Robot Programming II Documentation, リリース 1.0

リスト 4.3: 4-1c.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-1c 4-1c.c#include <stdio.h>#include <stdlib.h>45int* gIDList[10];67// 変数名や関数名の付け方の例

810

int main(void){11// 重要な変数や関数には長い詳しい名前をつける。

912char MemberNameList[100][64];

13
14
15

// 一時的にしか使わない、存在しない、重要でない変数や関数には短い名前をつける。

16

int i, j, tmp;

17
18
19

// 変数名の最初は名詞、関数名の最初は動詞にする。

20
21

int MemberIDList[10];22

ReadMemberID();

23
24// 内容がわかるように複数の言葉を組み合わせる。

25

int HowmanyAllMembers;

26
27
28

// 複数の単語の間をアンダーバー (_) で繋ぐ。

29

int number_of_all_member;

30
31
32

// 複数の単語を並べる際に、単語の最初の文字を大文字、残りを小文字にする。

33

int SelectedMemberIDList[10];

34
35
36

// 変数名の最初に属性が分かる文字を付加する。

37
38

int* pMemberIDList;

39

pMemberIDList = gIDList;

40
41// 抽象的な意味の単語を使わず、具体的な意味の単語を使う。

42

PutValue();43// どこに出力するのかわからない、曖昧(次のページに続く)46

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0(前のページからの続き)// ファイルに記録する
PrintValue(); // 表示する
SaveValue();

44
45
46
47

// 様々な解釈が可能な単語を使わず、誤解されない明確な意味の単語を使う。

48// ロボットの設定、どう設定するのか不明

49

SetRobot();

50

InitRobot(); // ロボットの初期化

51

exit(EXIT_SUCCESS);

52
53

}

4.4 グローバル変数とローカル変数
変数をグローバル変数として宣言するか、ローカル変数として宣言するか、どちらでも選択可能な場合、特別な理由がなければなるべくローカル変数として宣言する方がよい。グローバル変数にするとプログラム
内のどこでも内容を変更できてしまうので、間違いのもとになり保守時の信頼性が下がる。ローカル変数にすると、その変数を参照したり変更できるのはプログラム内の特定の部分に限定されるので保守性が向上する。更にローカル変数においても参照可能なプログラムの範囲をなるべく狭く必要最低限に限定するのがよい。
次に示す 2 つのプログラム 4-1.c, 4-2.c は同じ処理内容であるが、変数を宣言する位置が異なる。
★ 練習
どちらのプログラムが適切か、考えてみよう。その違いは何か。
リスト 4.4: 4-1.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-1 4-1.c#include <stdio.h>#include <stdlib.h>4
5#define

N

100

6
7

int

i, j, a[N][N];8intsum = 0;void

f1(void);

9
10
11
12

int main(void)

13

{

14

f1();

15
16
17

for (i = 0; i < N; i++) {for (j = 0; j < N; j++) {(次のページに続く)

4.4. グローバル変数とローカル変数47

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

sum += a[i][j];

18

}

19

}

20
21

printf("sum = %d\n", sum);22
23

exit(EXIT_SUCCESS);

24
25

}

26
27

void

28

{

f1(void)for (i = 0; i < N; i++) {29
30

for (j = 0; j < N; j++) {31

a[i][j] = i * j;
}

32

}

33
34

}

リスト 4.5: 4-2.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-2 4-2.c#include <stdio.h>#include <stdlib.h>4
5#define

N

100

6
7

void

f1(int a[][N]);8910

int main(void)
{
int

11

a[N][N], sum = 0;

12

f1(a);

13
14

for (int i = 0; i < N; i++) {15

for (int j = 0; j < N; j++) {16

sum += a[i][j];

17

}

18

}

19
20

printf("sum = %d\n", sum);21
22

exit(EXIT_SUCCESS);

23
24

}

25

(次のページに続く)48

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
26

void

27

{

f1(int a[][N])for (int i = 0; i < N; i++) {28

for (int j = 0; j < N; j++) {29

a[i][j] = i * j;

30

}

31

}

32
33

}
for (int i = 0; i < 10; i++) { ... } のような宣言の仕方は、古い C 言語の規格では認められていなかったが、C99 以降では使えるようになったので、積極的に使うとよい。
次のプログラムでは同じ名前の変数 a が複数の場所で宣言されている。
★ 練習
それぞれの printf() の場所で、どの a の値が参照されるか考えてみよう。その後、コンパイル、実行して確認しよう。
リスト 4.6: 4-3.c

1
2
3// gcc -Wall -ansi -std=c99 -o 4-3 4-3.c#include <stdio.h>#include <stdlib.h>

4
5

void

f1(void);6

void

f2(void);7

void

f3(int a);89struct {int a;

10
11

} b;

12
13

int

a = 1;

14
15

int main(void)

16

{

17

printf("a0 = %d\n", b.a = 10);18

printf("a1 = %d\n", a);

19

{

20

int

21

printf("a2 = %d\n", a);22{int a = 3; printf("a3 = %d\n", a);}

23

f1();

24

f2();

25

f3(a);

26

a = 2;}(次のページに続く)

4.4. グローバル変数とローカル変数49

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

printf("a4 = %d\n", a);2728

exit(EXIT_SUCCESS);

29
30

}

31
32

void

33

{

f1(void)int34

a = 4;

35

printf("a5 = %d\n", a);

36
37

}

38
39

void

40

{
printf("a6 = %d\n", a);4142f2(void)

}

43
44
45

void
{

printf("a7 = %d\n", a);46
47f3(int a)}

4.5 インデント、プログラムの整形プログラムを記述するときに適切なインデント（字下げ）を行い、かつプログラム全体で一貫した整形のルールに則って記述することは、後の保守時の可読性を確保するのに重要な意味を持つ。変数名やコメン
トをわかりやすく記述する、説明のドキュメントを整備するのとあわせて、プログラムの構造を理解しやすくするために一貫した整形のルールを持つことは同じ程度に重要である。
インデントと整形のルールは、ある程度標準的な暗黙のルールがあるが、必ずしもそれらに従う必要はな
い。多少標準と異なっても自分が決めたルールがあって、プログラム全体がそれに従っていれば良い。プ
ログラムの部分によって、記述の仕方が変化するのは、混乱の元になるのでよくない。
以下のプログラムはわざと字下げや空行を含めないで書いたものであるがこのような短いプログラムであっても、手を抜かずにきちんと整形のルールを適用する必要がある。短いプログラムしか書いていないと一貫した記述のルールが重要であることがわからないことがあるが、プログラミングを学ぶ初期の段階から一貫した整形のルールに従う習慣を身につけるべきである。
★ 練習
このプログラムを自分のルールに従って整形してみよう。

50

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0

リスト 4.7: 4-5.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// gcc -Wall -ansi -std=c99 -o 4-5 4-5.c#include<stdio.h>#include<stdlib.h>#include<string.h>
typedef
unsigned char
Uchar;
int
main(int argc, char *argv[]){Uchar
rgb[3];FILE*fp;
if (2 != argc) {fprintf(stderr, "Usage: %s file\n", argv[0]);exit(0);}if ((FILE *)NULL == (fp = fopen(argv[1], "w"))) {fprintf(stderr, "Error: [%s] can not open\n", argv[1]);exit(1);}
fprintf(fp, "P6\n256 256\n255\n");for (int i = 0; i < 256; i++) {for (int j = 0; j < 256; j++) {rgb[0] = i;rgb[1] = j;rgb[2] = 255 - i;fwrite(rgb, sizeof(Uchar), 3, fp);}}
fclose(fp);exit(0);}30

このプログラムは PPM 形式の画像ファイルを書き出す。実行時にデータをセーブするファイル名をコマ
ンドライン引数として一つ指定する。画像ファイルのフォーマットには、.jpg, .bmp. .png など様々なものがあるが.ppm はその一つである。データの圧縮がされないので操作が単純になり、簡潔なプログラムでも扱うことができる。
★ 練習
コンパイルして実行してみよう。出力された画像ファイル (zzz.ppm) をダブルクリックして表示してみよう。

$ gcc -Wall -ansi -std=c99 -o 4-5 4-5.c$ ./4-5 zzz.ppm

4.5. インデント、プログラムの整形

51

Robot Programming II Documentation, リリース 1.0

4.6 関数のプロトタイプ宣言
関数のプロトタイプ宣言は、プログラム中で関数が呼び出される前に、その関数の返り値の型と引数の型と個数を宣言する記述である。次に示す 4-10.c はプロトタイプ宣言をしないで記述したプログラムの例である。
リスト 4.8: 4-10.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-10 4-10.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)

6

{
f1();

7
8

exit(EXIT_SUCCESS);

9
10

}

11
12
13

void f1(void){
printf("f1\n");

14

f2();

15
16

}

17
18

void f2(void)

19{
printf("f2\n");

20
21

}
f1() と f2() は呼び出される前は関数の実体が定義されておらず、コンパイル時に警告が表示される。この場合、暗黙の型宣言がされているとみなされて処理される。ただし警告は表示されるがコンパイルは行われ、実行することはできる。この場合はたまたま実行結果に影響は現れていないが、結果がおかしくなることもある。

$ gcc -o 4-10 4-10.c
4-10.c: In function

main :

4-10.c:8:5: warning: implicit declaration of function

f1[-Wimplicit-function-

declaration]

,→

f1();
^~
4-10.c: At top level:
4-10.c:13:6: warning: conflicting types forf1

void f1(void)^~
4-10.c:8:5: note: previous implicit declaration off1

was here(次のページに続く)

52

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

f1();
^~
4-10.c: In function

f1 :4-10.c:16:5: warning: implicit declaration of function

f2 ; did you mean

f1 ? [-

Wimplicit-function-declaration]

,→

f2();
^~
f1
4-10.c: At top level:
4-10.c:19:6: warning: conflicting types forf2

void f2(void)^~
4-10.c:16:5: note: previous implicit declaration off2

was heref2();^~$ ./4-10f1f2
次に示す 4-10b.c のように、プログラム中で関数が呼び出される前に、その実体が定義されれば問題はな
く、正しいプログラムである。ただしすべての関数について呼び出される前の実体定義が必要であるので、関数が多くなるとその依存関係に基づいて各関数の事前定義をすべて満たすのは難しくなる。
リスト 4.9: 4-10b.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-10b 4-10b.c#include <stdio.h>#include <stdlib.h>45void f2(void)6{
printf("f2\n");

7
8

}

9
10

void f1(void)11{
printf("f1\n");

12

f2();

13
14

}

15
16

int main(void)

17

{

18

f1();

19

(次のページに続く)

4.6. 関数のプロトタイプ宣言

53

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

exit(EXIT_SUCCESS);

20
21

}
次に示す 4-10c.c のように、プロトタイプ宣言をプログラムの冒頭部分にまとめて記述することで、関数間の依存関係を気にする必要がなくなる。
リスト 4.10: 4-10c.c

12
3// gcc -Wall -ansi -std=c99 -o 4-10c 4-10c.c#include <stdio.h>#include <stdlib.h>45void f1(void);6

void f2(void);

7
8

int main(void)

9

{
f1();

10
11

exit(EXIT_SUCCESS);

12
13

}

14
15

void f1(void)

16

{

17

printf("f1\n");

18

f2();

19

}

20
21
22

void f2(void){
printf("f2\n");

23
24

}
次に示す 4-10d.c はプロトタイプ宣言がないが、f3() の定義と呼び出し時で引数の個数が異なるという致命的な間違いが含まれている。この場合でも警告は表示されるが、コンパイルは行われ、プログラムは実行できてしまう。警告を無視して実行すると、一見実行結果は得られているように見えるので、大きな問題となる。
リスト 4.11: 4-10d.c

12
3// gcc -Wall -ansi -std=c99 -o 4-10d 4-10d.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)

6{(次のページに続く)

54

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

f3(1);

7
8

exit(EXIT_SUCCESS);

9
10

}

11
12

int f3(int a, int b)

13

{

14

printf("f3 %d %d\n", a, b);

15

return(0);

16

}
★ 練習
コンパイル、実行して確認しよう。

$ gcc -o 4-10d 4-10d.c
4-10d.c: In function

main :

4-10d.c:8:5: warning: implicit declaration of function

f3[-Wimplicit-function-

declaration],→

f3(1);^~$ ./4-10d
f3 1 923427000
結果を見ると、1 個めの引数の値は正しく表示されるが、2 個めの引数は渡していないのでまったく意味のない値が表示されてしまう。

4-10d.c にプロトタイプ宣言を加えたのが 4-10e.c であるが、この場合は引数の個数が異なる間違いがコンパイル時に検出されエラーとなるので、容易に修正できる。
リスト 4.12: 4-10e.c
12
3// gcc -Wall -ansi -std=c99 -o 4-10e 4-10e.c#include <stdio.h>#include <stdlib.h>

4
5

int f3(int a, int b);

6
7

int main(void)8{
f3(1);

9
10

exit(EXIT_SUCCESS);

11
12

}

13

(次のページに続く)

4.6. 関数のプロトタイプ宣言

55

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
14

int f3(int a, int b)

15

{

16

printf("f3 %d %d\n", a, b);17

return(0);18}$ gcc -o 4-10e 4-10e.c
4-10e.c: In function

main :

4-10e.c:10:5: error: too few arguments to function

f3

f3(1);^~
4-10e.c:6:5: note: declared here
int f3(int a, int b);^~

4.7 個々の関数に割り当てる機能の考え方
プログラムの機能を関数として実現する場合、何を関数で行って、何を行わないか、作成する前に考える必要がある。その時には次の点を考慮するとよい。

• 一つの関数では一つの機能を実現する。様々な機能を詰め込まず、シンプルな機能とする。
• 複雑な機能の関数が必要な場合は、単純な機能の関数を組み合わせる。
• 様々な状況で使えるようになるべく汎用性を持たせることを考える。
• 関数の外部に実行時に必要となるデータや定義、定数が無い方がよい。
上記の事柄を実際のプログラムで確認してみよう。次のプログラムはいずれも整数配列の要素の総和を求める sum() の様々な実装である。4-11.c ではグローバル変数の配列に対して N 個の要素の和を求める。この実装では汎用性がなく、他の場面では全く使うことができない。
リスト 4.13: 4-11.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-11 4-11.c#include <stdio.h>#include <stdlib.h>4
5#define

N

10

6
7

int sum(void);

89

int ary[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};10
11

int main(void)

12{(次のページに続く)

56

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

int result;

13
14
15

result = sum();

16

printf("result : %d\n", result);17

exit(EXIT_SUCCESS);

18
19

}

20
21

int sum(void)22{
int sum = 0;23
24

for (int i = 0; i < N; i++) sum += ary[i];2526return(sum);

27
28

}
グローバル変数を対象とする場合でも、次の 4-11b.c のように関数の入出力の仕様を変更して引数として渡すようにすると、他の配列の和も求められるようになり、汎用性が向上する。グローバル変数であれば、引数としなくてもアクセスできるが、そこを敢えて引数にすることで汎用性が向上する。
リスト 4.14: 4-11b.c

1
2
3// gcc -Wall -ansi -std=c99 -o 4-11b 4-11b.c#include <stdio.h>#include <stdlib.h>4
5#define

N

10

6
7

int sum(int ary[]);89

int ary[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};10
11

int main(void)12{
int result;

13
14
15

result = sum(ary);

16

printf("result : %d\n", result);17

exit(EXIT_SUCCESS);

18
19

}

20
21

int sum(int ary[])22{(次のページに続く)

4.7. 個々の関数に割り当てる機能の考え方

57

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

int sum = 0;23
24

for (int i = 0; i < N; i++) sum += ary[i];2526return(sum);

27
28

}
次の 4-12.c では、総和を求めた後で関数内で結果を表示しているが、これは「計算」と「表示」を一つの関数で行っていて良い実装ではない。この実装では計算はさせたいが結果の表示が不要な場合に使うことができず、汎用性がない。
リスト 4.15: 4-12.c

1
2
3// gcc -Wall -ansi -std=c99 -o 4-12 4-12.c#include <stdio.h>#include <stdlib.h>4
5#define

N

10

6
7

int sum(int ary[]);8
9

int main(void)

10

{

11

int result;12

int ary[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};13

int ary2[N] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

14
15

result = sum(ary);

16

result = sum(ary2);

17exit(EXIT_SUCCESS);

18
19

}

20
21

int sum(int ary[])22{
int sum = 0;

23
24
25

for (int i = 0; i < N; i++) sum += ary[i];26printf("result : %d\n", sum);27

return(sum);

28
29

}
同様の実装でも、計算と表示を分離すると次の 4-13.c となり、後で結果を表示したい場合に使うことができ汎用性が増している。残っている問題点は、計算対象の要素の個数が外部で N として定義されているので、他の個数の場合は使うことができずその点の汎用性がない。

58

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0

リスト 4.16: 4-13.c
1
2
3// gcc -Wall -ansi -std=c99 -o 4-13 4-13.c#include <stdio.h>#include <stdlib.h>4
5#define

N

10

6
7

int sum(int ary[]);8
910

int main(void){11

int result, result2;

12

int ary[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};13

int ary2[N] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

14
15

result = sum(ary);

16

result2 = sum(ary2);

17

printf("result+result2 : %d\n", result+result2);

18

exit(EXIT_SUCCESS);

19
20

}

21
22

int sum(int ary[])23{
int sum = 0;24
25

for (int i = 0; i < N; i++) sum += ary[i];2627return(sum);

28
29

}
計算対象の個数が固定されている問題は、次の 4-14.c のように関数の引数として渡すことで他の個数でも計算できるようになり汎用性が増す。この実装では関数の外部に実行時に依存するデータや定義が一切ないので様々な場合で活用することができる。
リスト 4.17: 4-14.c

1
2
3// gcc -Wall -ansi -std=c99 -o 4-14 4-14.c#include <stdio.h>#include <stdlib.h>4
5#define

N

10

6
7

int sum(int ary[], int n);8(次のページに続く)

4.7. 個々の関数に割り当てる機能の考え方

59

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
9

int main(void)

10

{

11

int result, result2;

12

int ary[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};13

int ary2[N+1] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10};1415

result = sum(ary, N);

16

result2 = sum(ary2, N+1);

17

printf("result+result2 : %d\n", result+result2);

18

exit(EXIT_SUCCESS);

19
20

}

21
22

int sum(int ary[], int n)

23{
int sum = 0;24
25

for (int i = 0; i < n; i++) sum += ary[i];2627return(sum);

28
29

}

4.8 まとめ
この章の目標• プログラム作成時に必要とされる様々な基本スキルを理解して、実践できるようになる• プログラムの内容を説明するドキュメントの重要性を理解する• 変数名には意味を持たせる必要性を理解する• グローバル変数とローカル変数の違いを理解して適切に使い分ける• インデント、プログラムの整形の重要性を理解して実践する• 関数のプロトタイプ宣言の重要性を理解して実践する• 個々の関数に割り当てる機能の考え方を理解して、応用できる
練習問題

1. 有益でないコメントやドキュメントの特徴を説明しなさい。(e4-123.txt)
2. ER 学科の各学年に所属する学生の名簿を配列で作成する場合を想定して、よい変数名、悪い変数名を考えなさい。

3. 4-1.c, 4-2.c ではどちらのプログラムが適切か理由とともに説明しなさい。
4. 4-5.c を適切に整形して示しなさい。(e4-5.c)
60

第 4 章 プログラム作成の基本スキル

Robot Programming II Documentation, リリース 1.0
5. 以下に示す機能を持つプログラムを単一ファイルで作成しなさい。個々の機能は必要に応じて関数として実装しなさい。(e4-1.c)• 定数 N を 10 と定義する• N 個の整数が記録できる配列を確保する• キーボードから N 個の数値を入力して配列に記録する• 最大値を求める• 最小値を求める• 平均値を求める• N 個の数値の総和を求める• N 個の数値を小さい順に並べ替える• N 個の数値を大きい順に並べ替える• N 個の数値の中央値 (メディアン) を求める、N が偶数の場合は中央 2 値の平均値とする• それぞれの結果を表示する
6. 前のプログラムを分割コンパイルで生成できるように、ファイルを適切に分割しなさい。(e4-2.h,e4-2a.c, e4-2b.c, ...)
7. 前のプログラムを make で生成できるように定義ファイルを記述して make で生成しなさい。(Makeﬁle)4.8. まとめ

61

63

第 5 章 C++の様々な機能

ここからは C++ で C 言語から変更された機能、追加された機能を順に解説していく。同等の機能が C 言語に存在する場合は、 C 言語と対比させてその違いを示す。それぞれの機能の特徴を実例を通して示すために、サンプルプログラムが数多く登場するので、その記述内容を理解するように努めよう。

5.1 入出力
最初にデータの入出力の仕方を理解しよう。5-1.c は数や文字列の入出力を行う C 言語のプログラムである。
リスト 5.1: 5-1.c
1
2
3// gcc -Wall -ansi -std=c99 -o 5-1 5-1.c#include <stdio.h>#include <stdlib.h>4
5#define

N

100

6
7

int main(void)

8

{

9

int

10

float

f;

11

char

str[N];

a, b;

12
13

printf("Input a b : ");14

scanf("%d %d", &a, &b);

15

printf("%d %d\n", a, b);

16
17printf("Input f : ");18

scanf("%f", &f);

19

printf("%f\n", f);

20
21

printf("Input str : ");22

scanf("%s", str);

23

printf("%s\n", str);

24

exit(EXIT_SUCCESS);

25
26
27

}

Robot Programming II Documentation, リリース 1.0
実行結果は次のようになる。

$ ./5-2Input a b : 3 5
3 5
Input f : 1.25
1.25
Input str : abcdefg
abcdefg
同じ機能を C++ で実現するのが次の 5-2.cpp である。
★練習
コンパイルして実行してみよう。 C 言語と同じ出力になることを確認しよう。
リスト 5.2: 5-2.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 5-2 5-2.cpp#include <iostream>#include <cstdlib>#include <string>

5
6

int main(void)

7

{

8

int

9

float

10

std::string

a, b;
f;
str;

11
12

std::cout << "Input a b : ";13

std::cin >> a >> b;

14

std::cout << a << ' ' << b << std::endl;

15
16

std::cout << "Input f : ";17

std::cin >> f;18

std::cout << f << std::endl;

19
20

std::cout << "Input str : ";21

std::cin >> str;

22

std::cout << str << std::endl;

23

exit(EXIT_SUCCESS);

24
25

}
scanf() や printf() は使わず、入出力を行う ストリームオブジェクト として新たに用意された cin, cout を使用する。<< と >> は C 言語ではデータのシフトを行う演算子であったが、新たに入出力時にデータをスト
リームオブジェクトに送る機能が追加された。endl は改行を表す。文字列は C 言語では char の配列として表現されていたが、C++ では専用のデータ型 string が用意された。サイズは必要に応じて確保されるの64

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0
で、文字列の長さを意識して必要な大きさを確保する必要がなくなった。

5-3.c は整数や浮動小数点数を様々なフォーマットで出力する仕方を示している。
★練習コンパイルして実行してみよう。
リスト 5.3: 5-3.c
1
2
3// gcc -Wall -ansi -std=c99 -o 5-3 5-3.c#include <stdio.h>#include <stdlib.h>

4
5

int main(void)

6

{

7

int

8

float

f = 1.2345;

9doubled = 1.2345;

a = 16;10
11

printf("[%d]\n", a);12

printf("[%4d]\n", a);13

printf("[%04d]\n", a);14

printf("[%o]\n", a);15

printf("[%x]\n", a);16

printf("[%04x]\n-----\n", a);1718

printf("[%f]\n", f);

19

printf("[%.4f]\n", f);20

printf("[%.15f]\n-----\n", f);

21
22

printf("[%f]\n", d);23

printf("[%.4f]\n", d);24

printf("[%.15f]\n", d);25

printf("[%.30f]\n-----\n", d);26
27

if (16 == a) printf("equal\n");

28

else

printf("not equal\n");29
30

if (1.2345 == f) printf("equal\n");

31

else

printf("not equal\n");32
33

if (1.2345 == d) printf("equal\n");

34

else

printf("not equal\n");35

exit(EXIT_SUCCESS);

36
37

}
%4d は 4 桁での出力、 %04d は空白を 0 で埋める指示、%o は 8 進数での出力、%x は 16 進数での出力、5.1. 入出力

65

Robot Programming II Documentation, リリース 1.0%.4f は小数点以下 4 桁で出力を表している。角括弧 [, ] で値がかこんであるのは、空白や空白を埋める文
字がどこにあるかをわかりやすくするためである。気をつける点として、浮動小数点数は誤差を含むので比較をする時には表現精度に注意することがある。

$ ./5-3
[16]
[

16]

[0016][20][10][0010]----[1.234500][1.2345][1.234500050544739]----[1.234500][1.2345][1.234500000000000][1.234499999999999930722083263390]----equal
not equal
equal
このプログラムの最後の比較の部分は、入出力とは別の重要な事柄を表している。整数型 int の変数が表す値は誤差が含まれないので、このプログラムのような比較は必ず正しい結果が得られる。一方、実数型

ﬂoat, double の変数が表す値は値によって誤差が含まれるので、比較の結果は想定した結果になる場合とならない場合がある。今回は double 型の比較でたまたま等しいという結果になったが、これにしても厳密には同じ値を表しているわけではない (小数点以下 30 桁表示を見るとわかる)。実数型 ﬂoat, double の表現ではほとんどの場合で誤差が存在するので、今回のように "==" で比較するのは危険である。常に変数が含む誤差を想定したプログラムにする必要がある。

5-3.c と同じ出力を得る C++ のプログラムが以下になる。出力のフォーマットの指定の仕方が異なるので、新たな記述の仕方を理解する必要がある。
★練習コンパイルして実行してみよう。
リスト 5.4: 5-4.cpp
12
3
4// g++ -Wall -ansi -std=c++11 -o 5-4 5-4.cpp#include<iostream>#include<cstdlib>#include<iomanip>

5
6

int

main(void)(次のページに続く)66

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
7

{

8

int

9

float

f = 1.2345;

10

doubled = 1.2345;

a = 16;

11
12

std::ios::fmtflags flagsSaved = std::cout.flags();

13

char fillSaved = std::cout.fill();

14
15

std::cout << "[" << a << "]" << std::endl;

16

std::cout << "[" << std::setw(4) << a << "]" << std::endl;

17

std::cout.fill('0');18

std::cout << "[" << std::setw(4) <<a << "]" << std::endl;

19

std::cout << "[" << std::oct << a << "]" << std::endl;

20

std::cout << "[" << std::hex << a << "]" << std::endl;

21

std::cout << "[" << std::hex << std::setw(4) << a << "]" << std::endl;

22

std::cout << "-----" << std::endl;

23
24

std::cout << "[" << f << "]" << std::endl;

25

std::cout << "[" << std::fixed << std::setprecision(6) << f << "]" << std::endl;

26

std::cout << "[" << std::setprecision(15) << f << "]" << std::endl;

27

std::cout << "-----" << std::endl;

28
29

std::cout << "[" << d << "]" << std::endl;

30

std::cout << "[" << std::fixed << std::setprecision(6) << d << "]" << std::endl;

31

std::cout << "[" << std::setprecision(15) << d << "]" << std::endl;

32

std::cout << "[" << std::setprecision(30) << d << "]" << std::endl;

33
34

std::cout.flags(flagsSaved);35

std::cout.fill(fillSaved);

36
37// float, double の比較については同じ内容なので省略

38

exit(EXIT_SUCCESS);

39
40

}
11, 12 行目は出力の設定を記録する処理、26, 27 行目は記録した設定を使って当初の設定に戻す処理である。C 言語では出力の指定は 1 回限り有効であったが、C++では一度設定するとずっと有効なものがある
ので、もとの設定に戻したい場合は、このような記録と復帰の処理が必要になることがある。

$ ./5-4
[16]
[

16]

[0016][20][10](次のページに続く)

5.1. 入出力

67

Robot Programming II Documentation, リリース 1.0(前のページからの続き)[0010]----[1.2345][1.234500][1.234500050544739]----[1.234500000000000][1.234500][1.234500000000000][1.234499999999999930722083263390]
C 言語、C++ ともに、上記以外にも多くの出力フォーマットの指定の仕方があるので、必要な場合は自分で調べるとよい。

5.2 スコープ、名前空間, using
変数の有効範囲や生存期間については 4.3. 「グローバル変数とローカル変数」の節で取り上げたが、ここでは変数や関数の有効範囲について、(分割コンパイルを前提とした) 複数ファイル間での特に名前の重複の問題や扱われ方について再度確認する。C++ では名前の重複による問題を巧みに回避する仕組みが新たに取り入れられている。

5.2.1 名前の衝突、重複により生じる問題
以下の 5-7a.cpp, 5-7b.cpp では、変数 a と関数 f1() が同じ名前で定義されている。C 言語でも同じ記述が可能で、問題となる点も同じである。
★練習
コンパイルして実行してみよう。この２つのソースは統合されて一つの実行ファイルになるので、名前の重複によりどのような問題が生じるか見てみよう。
リスト 5.5: 5-7a.cpp
1
2
3// g++ -Wall -ansi -std=c++11 -o 5-7 5-7a.cpp 5-7b.cpp#include <cstdio>#include <cstdlib>

4

6

void
void

f1(void);f2(void);7

int

a = 1;

5

8
9

int main(void)

10{
printf("main a = %d\n", a);11(次のページに続く)

68

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
12

f1();

13

f2();

14

exit(EXIT_SUCCESS);

15
16

}

17
18

void

19

{
printf("main f1\n");20
21

f1(void)}

リスト 5.6: 5-7b.cpp
12// 5-7b.cpp#include <cstdio>

3
4
5
6

static void f1(void);// void f1(void);// extern void f1(void);

7
8

void

f2(void);

9
10
11
12

static int a = 2;// int a = 2;// extern int a;

13
14

void

15

{
printf("sub f1\n");16
17f1(void)

}

18
19

void

20

{
printf("sub a = %d\n", a);

21

f1();

22
23

f2(void)}
5-7a.cpp では変数と関数の実体が宣言、定義されており、ここに存在する。この宣言の仕方では、他のファイルからも必要に応じて参照することができる。f2() の実体はここにはなく、他のファイルに実体があるとしてプロトタイプ宣言だけが記述されている。

$ ./5-7
main a = 1
main f1
sub a = 2
(次のページに続く)

5.2. スコープ、名前空間, using

69

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

sub f1
5-7b.cpp では static 宣言が付加されているが、これにより他のファイルに存在する同じ名前の変数、関数と同時に存在させることが可能となる。static を付けた変数、関数はそのファイル内でだけ参照することができ、他のファイルからは参照できない。5-7a.cpp にある同名の変数、関数とは別の存在となる。f2() の実体
はここにあり、main() からの参照を許している。

6,7 行目のコメントアウトされている宣言は外部の関数を参照する場合の記述になる。その場合、このファ
イルでは f1() を定義できないので、15--18 行を削除する必要がある。削除しないとエラーとなりコンパイ
ルができない。

12, 13 行目のコメントアウトされている宣言は外部の変数を参照する場合の記述になる。12 行目は間違いでこれは通らない。変数の場合は必ず extern が必要になる。13 行目は正しい記述で 5-7a.cpp の実体を参照
する。この場合値の初期化はここではできない。
このように、従来の C 言語の文法では同名の変数や関数を複数の実体として定義するには、static を付ける
か、{ } で囲んだブロックの中に置く必要があり大きく制限されている。

5.2.2 名前空間の導入による名前の衝突の回避
C++ では、単一のファイルの中でも同名の変数や関数を複数の実体として定義することができる 名前空間
namespace と呼ばれる新しい概念と記法が導入されている。次に示す 5-8.cpp では名前空間を用いて、変数a と関数 f1() を 3 個の異なる実体として定義している。
★練習コンパイルして実行してみよう。
リスト 5.7: 5-8.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-8 5-8.cpp#include <cstdio>#include <cstdlib>

4
5

int

a = 0;

6void

f1(void);78

namespace n1 {9

int a = 1;

10void f1(void)11{
printf("n1 a = %d\n", a);

12
13
14

}
}

15
16

namespace n2 {17

int a = 2;
(次のページに続く)70

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
18

void f1(void)

19{
printf("n2 a = %d\n", a);

20
21

}

22

}

23
24

int main(void)

25

{

26

printf("a = %d\n", a);27

printf("a = %d\n", ::a);28

printf("a = %d\n", n1::a);29

printf("a = %d\n", n2::a);

30
31

f1();

32

::f1();

33

n1::f1();34

n2::f1();35

exit(EXIT_SUCCESS);

36
37

}

38
39

void

40

{
printf(":: a = %d\n", a);4142f1(void)}$ ./5-8
a = 0
a = 0
a = 1
a = 2
:: a = 0:: a = 0n1 a = 1n2 a = 2
n1, n2 の２個の名前空間内とグローバルな名前空間に同じ名前で存在する。プログラムからは名前空間を指定することでそれぞれを区別して参照することができる。
名前空間は現実世界における「住所」や「所属」に相当すると考えることができる。同姓同名の人、例えば「鈴木一郎」さんが複数人いるとき、そのままでは区別ができないが、春日井市松本町の「鈴木一郎」さんと春日井市出川町の「鈴木一郎」さん、または ER の「鈴木一郎」さんと EU の「鈴木一郎」さんのように、他の何らかの属性を加えることで区別ができるようになる。同じ考え方をプログラミングの変数や関数の区別に取り入れたものといえる。
変数と関数どちらの場合でも、何も付けない場合はグローバルな名前空間、:: を付けた場合はグローバル

5.2. スコープ、名前空間, using

71

Robot Programming II Documentation, リリース 1.0
な名前空間、n1:: や n2:: のように名前空間を指定した場合はそこに属する実体を参照する。

5.2.3 using 宣言による名前空間の指定
よく使う名前空間は using 宣言をすることで名前空間の指定を省略することができる。5-9.cpp では usingnamespace n2; を記述することで、名前空間の指定をしない場合は n2 が指定されたものとして扱われる。
よって、35, 36 行目の b の指定は同じ意味となる。ただし、34, 38 行目のように曖昧になる場合はエラーとなり省略できない。using 宣言は次の using 宣言が現れるまで効力を持つ。
リスト 5.8: 5-9.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-9 5-9.cpp#include <cstdio>#include <cstdlib>

4
5

int

a = 0;

6void

f1(void);78

namespace n1 {9

int a = 1;

10void f1(void)11{
printf("n1 a = %d\n", a);

12
13
14

}
}

15
16

namespace n2 {17

int a = 2;18

void f1(void)

19{
printf("n2 a = %d\n", a);

20
21

}

22

int b = 12;23

void f2(void)

24{
printf("n2 b = %d\n", b);

25
26

}

27

}

28
29

using namespace n2;

30
31

int main(void)

32

{

33

//

34

printf("a = %d\n", a); // この場合 ::a と n2::a の区別ができずエラー
printf("b = %d\n", b);(次のページに続く)

72

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

printf("b = %d\n", n2::b);

35
36
37

//

38
39
40

f1(); // この場合 ::f1() と n2::f1() の区別ができずエラー::f1();
n1::f1();n2::f1();

41
42

f2();

43

n2::f2();44

exit(EXIT_SUCCESS);

45
46

}

47
48

void

49

{

50

//

51
52

f1(void)

printf(":: a = %d\n", a); // この場合 ::a と n2::a の区別ができずエラー
printf(":: a = %d\n", ::a);}$ ./5-9b = 12b = 12:: a = 0n1 a = 1n2 a = 2n2 b = 12
n2 b = 12
5-10.cpp は 5-2.cpp に対して using namespace std; により、std:: を省略できるようにしたものである。std には入出力でよく使われる定義が多く含まれるので、このように指定することがよくある。5-10.cpp は記述量が減ってスッキリしたことがわかる。
リスト 5.9: 5-10.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 5-10 5-10.cpp#include <iostream>#include <cstdlib>#include <string>

5
6

using namespace std;

7
8

int main(void)

9

{

10

int

11

float

a, b;
f;
(次のページに続く)

5.2. スコープ、名前空間, using73

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

string

12

str;

13
14

cout << "Input a b : ";15cin >> a >> b;

16

cout << a << ' ' << b << endl;

17
18

cout << "Input f : ";19

cin >> f;20

cout << f << endl;

21
22

cout << "Input str : ";23

cin >> str;

24

cout << str << endl;

25

exit(EXIT_SUCCESS);

26
27

}
$ ./5-10
Input a b : 3 5
3 5
Input f : 1.5
1.5
Input str : abcde
abcde
大規模なプログラムでは、変数や関数の個数、ファイル数が膨大になるので、名前の衝突をいかにして防ぐかは重要な課題である。名前空間はそのための有効な手法となる。例として、何らかのまとまった処理の単位ごとに異なった名前空間を割り当てるとか、作成者毎に異なった名前空間を割り当てることで名前の衝突を防ぐことができるようになる。using 宣言は不用意に使うと副作用によりトラブルの原因になるので、影響する範囲をよく理解して使う必要がある。using 宣言はヘッダで使うと、その後のすべての部分に影響を及ぼすのでヘッダでは使わない方がよいとされている。

5.3 参照、ポインタとの機能の違い
まず C 言語、C++ におけるポインタによる引数の受け渡しについて復習する。
★練習
次のプログラム中のそれぞれの printf() による出力がどうなるか考えてみよう。その後、コンパイル、実行
して結果を確認しよう。
リスト 5.10: 5-11.cpp
12// g++ -Wall -ansi -std=c++11 -o 5-11 5-11.cpp#include <cstdio>(次のページに続く)74

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
3#include <cstdlib>

4
5

void

f1(int a, int b);

6

void

f2(int *a, int *b);7
8

int main(void)9{
int a = 1, b = 2;

10
11
12

f1(a, b);13

printf("a = %d, b = %d\n", a, b);14

f2(&a, &b);

15

printf("a = %d, b = %d\n", a, b);

16

exit(EXIT_SUCCESS);

17
18

}

19
20
21

void
{

f1(int a, int b)

22

a = 3;

23b = 4;

24

printf("a = %d, b = %d f1()\n", a, b);

25

}

26
27

void

28

{

f2(int *a, int *b)*a = 5;2930*b = 6;31

printf("a = %d, b = %d f2()\n", *a, *b);32}

$ ./5-11
a = 3, b = 4 f1()
a = 1, b = 2
a = 5, b = 6 f2()
a = 5, b = 6関数 f1() の呼び出しでは、引数として main() にある a, b とは別の変数が用意され、そこに a, b の値がコピーされ f1() が呼び出される ( 値渡し )。よって、呼び出された先で a, b の値が変更されても本家には影響せず、main() の方では値が変わらない。 f2() の呼び出しでは a, b のアドレスが関数に渡され ( アドレス渡
し、ポインタ )、呼び出された先では本家と同じ場所をアクセスするので本家の値が書き換わる。

C++では、この２種類の引数の渡し方に加えて、第３の方法として 参照 , 参照渡し という方法が追加された。参照渡しを用いると、ポインタの場合と同じように呼び出された先で値を変更できる。参照とポイン
タを比較すると、それぞれの機能の実装の仕方と実行時の処理の内容は異なるが、表面的にはできることが似ていると言える。次に示す 5-13.cpp では f3() で参照の記述が出てくるが、ポインタに比べると記述量

5.3. 参照、ポインタとの機能の違い

75

Robot Programming II Documentation, リリース 1.0
が少なくなっていることがわかる。

f1() と f3() を比較すると、関数の引数の宣言で変数名の前に & が付加されている一点だけが異なっている。
これにより f3() は参照渡しとなり、関数内で引数の値を書き換えられるようになる。
リスト 5.11: 5-13.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-13 5-13.cpp#include <cstdio>#include <cstdlib>

4
5
6
7

void

f1(int a, int b);

void
void

f2(int *a, int *b); // ポインタ
f3(int &a, int &b); // 参照

8
9

int main(void)10{
int a = 1, b = 2;

11
12
13

f1(a, b);14

printf("a = %d, b = %d\n", a, b);15
16f2(&a, &b);

17

printf("a = %d, b = %d\n", a, b);18
19f3(a, b);20

printf("a = %d, b = %d\n", a, b);21

exit(EXIT_SUCCESS);

22
23

}

24
25

void

26

{

f1(int a, int b) // 値渡し、新たに引数用の変数が用意される

27

a = 3;

28

b = 4;

29

printf("a = %d, b = %d f1()\n", a, b);

30

}

31
32

void

33

{

f2(int *a, int *b) // アドレス渡し、ポインタ*a = 5;3435*b = 6;36

printf("a = %d, b = %d f2()\n", *a, *b);

37

}

38
39

void

40

{

f3(int &a, int &b) // 参照渡し
(次のページに続く)

76

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
41

a = 7;

42b = 8;

43

printf("a = %d, b = %d f3()\n", a, b);44}$ ./5-13
a = 3, b = 4 f1()
a = 1, b = 2
a = 5, b = 6 f2()
a = 5, b = 6
a = 7, b = 8 f3()
a = 7, b = 8
変数を参照として宣言すると、他の変数の別名として機能するようになる。次の 5-14.cpp では b を a の参照として宣言しているが、これにより a, b のどちらを操作しても a の値を変更できる。
リスト 5.12: 5-14.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-14 5-14.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
int a = 1, &b = a; // b は a の別名となる、実体は同じ

789a = 2; // どちらを操作しても結果は同じ

10

printf("a = %d, b = %d\n", a, b);1112

b = 3; // どちらを操作しても結果は同じ

13

printf("a = %d, b = %d\n", a, b);

14exit(EXIT_SUCCESS);

15
16

}

$ ./5-14
a = 2, b = 2
a = 3, b = 3
参照はポインタと同じことができるが、機能には次に示す違いがある。

• 参照は宣言時に必ず初期化、指し示す変数を指定しなければならない。ポインタは宣言と指し示す変数の指定を別に行うことができる。

• 参照は指し示す先を後で変更することはできない。ポインタは後で指し示す先を変更できる。
次に示す 5-15.cpp ではこの機能の違いを示している。

5.3. 参照、ポインタとの機能の違い

77

Robot Programming II Documentation, リリース 1.0

リスト 5.13: 5-15.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-15 5-15.cpp#include <cstdio>#include <cstdlib>

4
5

void

f1(int a , int b ); // 通常の関数呼び出し

6

void

f2(int *a, int *b); // アドレス渡し、ポインタ

7

void

f3(int &a, int &b); // 参照

8
9

int main(void)10{
int a = 1, b = 2;

11
12
13

f1(a, b);14

printf("a = %d, b = %d\n\n", a, b);1516// ポインタ /////////////////////////////////////////////////

17

a = 1, b = 2;18
19

f2(&a, &b);

20

printf("a = %d, b = %d\n", a, b);

21
22
23

//

24
25
26
27

int* c = &a; int* d = &b;
int *c = &a; int *d = &b; // このようにも書ける
f2(c, d);printf("c = %d, d = %d\n\n", *c, *d);c = &b; b = 999; // ポインタが指す先は変更できる
printf("c = %d, d = %d\n\n", *c, *d);2829// 参照 /////////////////////////////////////////////////////

30
31

a = 1, b = 2;32

f3(a, b);33

printf("a = %d, b = %d\n", a, b);34

int& e = a; int& f = b; // 初期化は必須、宣言時のみできる

35
36

//

37
38
39
40

int &e = a; int &f = b; // このようにも書ける
f3(e, f);printf("e = %d, f = %d\n\n", e, f);e = b; b = 999; // 参照先の変更は不可、値が代入されるだけ
printf("e = %d, f = %d\n\n", e, f);41

exit(EXIT_SUCCESS);

42
43

}
(次のページに続く)

78

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
44
45

void

46

{

f1(int a, int b) // 値渡し、新たに引数用の変数が用意される

47

a = 3;48

b = 4;
printf("a = %d, b = %d f1()\n", a, b);

49
50

}

51
52

void

53

{

f2(int *a, int *b) // アドレス渡し、ポインタ

54*a = 5;55*b = 6;
printf("a = %d, b = %d f2()\n", *a, *b);

56
57

}

58
59

void

60

{

f3(int &a, int &b) // 参照渡し62

a = 7;
b = 8;

63

printf("a = %d, b = %d f3()\n", a, b);

61

64

}

$ ./5-15
a = 3, b = 4 f1()
a = 1, b = 2
a = 5, b = 6 f2()
a = 5, b = 6
a = 5, b = 6 f2()
c = 5, d = 6
c = 999, d = 999
a = 7, b = 8 f3()
a = 7, b = 8
a = 7, b = 8 f3()
e = 7, f = 8
e = 8, f = 999
ポインタを使うと内容を表す時に必ず*a のように記述する必要があり、記述が煩雑になる。一方、参照では通常の変数のように記述できるので、記述を簡潔にできるのが参照を使う一番のメリットになると言える。
参照を使う時に気をつけることとして、関数を呼び出す側では、その関数が値渡しなのか参照渡しなのか、
定義を見ないと区別できないという点がある。

5.3. 参照、ポインタとの機能の違い

79

Robot Programming II Documentation, リリース 1.0

5.4 動的メモリ確保 new, delete、malloc, free
実行するときでないと配列の大きさが決まらない場合がある。例として、画像ファイルを表示や編集する
とき、音声データを加工するとき、大量の数値に対して計算をするときなどである。そのような場合のために、C 言語、C++ ともに実行時に 動的に変数の領域を確保する機能がある。この機能は、サイズを指定してメモリを確保する命令と、使い終わった時にその領域を解放して OS に返す命令である。それぞれ、C
言語では malloc(), free() となり、 C++ では new と delete となる。
次に示す 5-16.cpp は、C++のプログラムであるが、メモリの確保と解放は malloc(), free() を使っている。起動後に配列の個数を入力して、指定された大きさのメモリを割り当てている。プログラムが終了する前に解放している。
★練習コンパイルして実行してみよう。
リスト 5.14: 5-16.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-16 5-16.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

int main(void)

8

{

9

int *ary;

10

long int n;

11
12

cout << "Input array size => ";13

cin >> n;1415

ary = (int *)malloc(sizeof(int) * n);16for (long int i = 0; i < n; i++) {ary[i] = i;17cout << ary[i] << " ";

18
19

}

20

cout << endl;

21

free(ary);22

exit(EXIT_SUCCESS);

23
24

}

$ ./5-16
Input array size => 10
0 1 2 3 4 5 6 7 8 9
malloc() には必要なバイト数を指定するが、あまり大きなサイズを指定して確保できずに失敗すると、返80

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0
り値は NULL になる。その場合、領域は確保されていないので、そのままアクセスしようとするとエラーになる。返り値が NULL の場合はそのまま継続して実行できないので、次の 5-17.cpp のようにエラー処理を用意しておく必要がある。
リスト 5.15: 5-17.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-17 5-17.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

int main(void)

8

{

9

int *ary;

10

long int n;

11
12

cout << "Input array size => ";13

cin >> n;1415

ary = (int *)malloc(sizeof(int) * n); // C 言語, int 型で n 個分の領域確保

16

if (NULL == ary) { // 確保できない時のエラー処理

17

cerr << "Too large size." << endl;

18

exit(EXIT_FAILURE);

19

}

20

for (long int i = 0; i < n; i++) {ary[i] = i;

21
22

//

cout << ary[i] << " ";
}

23
24

cout << endl;

25

free(ary); // C 言語, 使い終わったら忘れずに解放する26

exit(EXIT_SUCCESS);

27
28

}

$ ./5-17
Input array size => 10
0 1 2 3 4 5 6 7 8 9
C++ の new と delete を使うと次の 5-18.cpp のように書ける。記述の仕方は変わっているが、やっていることは同じである。
★練習
コンパイルして実行してみよう。

5.4. 動的メモリ確保 new, delete、malloc, free81

Robot Programming II Documentation, リリース 1.0

リスト 5.16: 5-18.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-18 5-18.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

int main(void)8{
int *ary;
long int n;

9
10
11
12

cout << "Input array size => ";13

cin >> n;1415

ary = new int[n];16for (long int i = 0; i < n; i++) {17

ary[i] = i;18

cout << ary[i] << " ";
}

19
20

cout << endl;

21

delete[] ary;22

exit(EXIT_SUCCESS);

23
24

}

$ ./5-18
Input array size => 10
0 1 2 3 4 5 6 7 8 9
こちらも確保に失敗すると返り値が NULL となるので、同じようにエラー処理が必要となる。new の場合は失敗すると、それを表す例外を発生させるので、このプログラムでは例外発生を抑制する new(nothrow)として、エラー処理ができるようにしてある。
リスト 5.17: 5-19.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 5-19 5-19.cpp#include <iostream>#include <cstdlib>#include <new>5
6

using namespace std;

7
8

int main(void)(次のページに続く)

82

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
9

{

10

int *ary;

11

long int n; // 64 bits, 大きなサイズの配列を扱うための準備

12
13

cout << "Input array size => ";14

cin >> n;1516

ary = new(nothrow) int[n]; // C++, int 型で n 個分の領域確保

17

if (NULL == ary) { // 確保できない時のエラー処理

18

cerr << "Too large size." << endl;

19

exit(EXIT_FAILURE);

20

}

21

for (long int i = 0; i < n; i++) {ary[i] = i;

22
23

//

cout << ary[i] << " ";

24

}

25

cout << endl;

26

delete[] ary; // C++, 使い終わったら忘れずに解放する27

exit(EXIT_SUCCESS);

28
29

}
$ ./5-19
Input array size => 10
0 1 2 3 4 5 6 7 8 9配列を確保できる限界の大きさを調べた結果が以下である。表示を簡潔にするために、配列中の要素に代入した値は表示させていない。使用した PC のメモリ容量は 16GB である。

$ ./5-19
Input array size => 2000000000(正常終了)...
...
$ ./5-19
Input array size => 4000000000(正常終了)$ ./5-19
Input array size => 4100000000
強制終了

...
...
$ ./5-19
Input array size => 4700000000
強制終了(次のページに続く)

5.4. 動的メモリ確保 new, delete、malloc, free83

Robot Programming II Documentation, リリース 1.0(前のページからの続き)$ ./5-19
Input array size => 4800000000
Too large size.
この結果を見ると、int 型で 40 億個までは問題なく確保でき、41 億個から 47 億個までは強制終了させられた。48 億個以降は確保できないことを認識して正常終了している。41 億個から 47 億個までは、本来はOS 側で正常に確保されるか、確保できないと通知すべきである。しかし現状は、確保しようとして OS が処理を進めた後に内部で何らかの問題が発生したことが予想され、強制終了となっている。
ここまでは配列を確保するという前提でサンプルプログラムを作成したが単一の変数を確保することもできる。その場合は下記の★★★が付いている２箇所の書き方が変わる。
リスト 5.18: 5-20.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 5-20 5-20.cpp#include <iostream>#include <cstdlib>#include <new>5
6

using namespace std;

7
8

int main(void)9{
int *p;101112

p = new(nothrow) int; // ★★★

13

if (NULL == p) { // 確保できない時のエラー処理

14

cerr << "Too large size." << endl;

15

exit(EXIT_FAILURE);

16

}

17

*p = 1;18

cout << *p << endl;

19

delete p; // ★★★

20exit(EXIT_SUCCESS);

21
22

}

$ ./5-20
1
malloc() や new による動的なメモリ領域確保に関する注意として、malloc() と free()、 new と delete は必ずセットで使うことがある。malloc() と delete、new と free() のような組み合わせでは使用してはいけない。

84

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0

5.5 定数の定義 const, #deﬁne
C 言語では定数を定義する場合には #deﬁne が使われることが多い。
リスト 5.19: 5-21.c
1
2
3// gcc -Wall -ansi -std=c99 -o 5-21 5-21.c#include <stdio.h>#include <stdlib.h>4
5#defineARYSIZE

100

6
7

int main(void)

8

{
int

9

a[ARYSIZE];

10

for (int i = 0; i < ARYSIZE; i++) {11

a[i] = i;12

printf("%d ", a[i]);

13
14

}

15

printf("\n");16

exit(EXIT_SUCCESS);

17
18

}

$ ./5-21
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31␣
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59␣

,→60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87␣

,→88 89 90 91 92 93 94 95 96 97 98 99,→

C++ ではこのような場合に const を付けた変数を使う。 const は値を変更できない変数を表す。変更できな
いため定数として扱うことができる。const を付けた変数は後から代入ができないため、宣言時の初期化により必要な値を代入する。
リスト 5.20: 5-22.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-22 5-22.cpp#include <iostream>#include <cstdlib>

4
5

const int ARYSIZE = 100;

6
7

using namespace std;

89

int main(void)(次のページに続く)5.5. 定数の定義 const, #deﬁne85

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
10

{
int

11

a[ARYSIZE];

12

for (int i = 0; i < ARYSIZE; i++) {13

a[i] = i;14

cout << a[i] << " ";

15
16

}

17

cout << endl;

18

exit(EXIT_SUCCESS);

19
20

}

$ ./5-22
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31␣
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59␣

,→60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87␣

,→88 89 90 91 92 93 94 95 96 97 98 99,→

C 言語にも const は存在するが、使用できる場面が C++ よりも限定されているため、C++ ほどは活用されてこなかった。例として、5-22.cpp と同じように配列のサイズを指定する 5-23.c は以前の C 言語の仕様ではコンパイル時にエラーとなり使えなかった。その後 C++ の機能を一部取り込んで仕様が拡張されてきたため、新しいコンパイラでは C++ と同じように使えるようになった。
リスト 5.21: 5-23.c
1
2
3// gcc -Wall -ansi -std=c99 -o 5-23 5-23.c#include <stdio.h>#include <stdlib.h>45

const int ARYSIZE = 100;

6
7

int main(void)

8

{
int

9

a[ARYSIZE];

10

for (int i = 0; i < ARYSIZE; i++) {1112a[i] = i;13

printf("%d ", a[i]);

14

}

15

printf("\n");16

exit(EXIT_SUCCESS);

17
18

}

86

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0$ ./5-23
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31␣
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59␣

,→60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87␣

,→88 89 90 91 92 93 94 95 96 97 98 99,→#deﬁne と const の違いは、コンパイル前に文字列の置き換えとして処理されるか、言語の一部としてコン
パイラのチェックがされるかの違いとなる。文字列の置き換えでは想定できない副作用が生じて、かつコンパイラでは検出できないことがあるので、どちらでも記述可能な場合はなるべく const を使うのが良いとされている。
ポインタに対して const を指定する場合は、記述の仕方が複数あり、それぞれで表す内容と効果が異なる
ので気をつける必要がある。
リスト 5.22: 5-24.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-24 5-24.cpp#include <iostream>#include <cstdlib>

4
5

int main(void)

6

{

7

int

a = 1, b = 2;8

const int*

p1 = &a;9

int* const

p2 = &a;

10

const int* const p3 = &a;

11

int const*p4 = &a;// *p1, 変数の値が変更不可になる// p2, アドレスが変更不可になる// p3, *p3 の両方が変更不可// *p1, 変数の値が変更不可になる

12

p1 = &b; // p1 が保持するアドレスを書き換える

13
14

//

*p1 = 3; // p1 が指し示す変数の値を書き換える、エラー//

p2 = &b; // エラー*p2 = 3;////p3 = &b; // エラー*p3 = 3; // エラー

15
16
17
18
19
20
21

p4 = &b;

22
23

//

*p4 = 3; // エラー

24

exit(EXIT_SUCCESS);

25
26

}
値が変更されては困る変数、変更されることを想定していない変数、変更すべきでない変数の定義や関数間の受け渡しでは、極力 const を付けることが推奨される。const を付けることで想定外の値の変更によるプログラムの誤動作を事前に防ぐことができる。
★練習

5.5. 定数の定義 const, #deﬁne

87

Robot Programming II Documentation, リリース 1.0
次のプログラムでは、文字列の長さを求める関数 int strlength(char str[]) を作成しているが正しく動作しない。原因は何か考えてみよう。コンパイル、実行してみよう。
リスト 5.23: 5-25.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-25 5-25.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

const int STRBUF = 10;

8
9

int

strlength(char str[]);10
11

int main(void)

12

{
char

13

a[STRBUF] = "abcdefg";

14

cout << a << " " << strlength(a) << endl;

15
16

exit(EXIT_SUCCESS);

17
18

}

19
20

int

21

{

strlength(char str[])22for (int i = 0; i < STRBUF; i++) {23

if (str[i] = 0) return(i);}24return(-99);

25
26

}
$ ./5-25
abcdefg -99
strlength() では str[] を変更する機能はないので、引数の定義に const を加えると次の 5-26.cpp になる。これをコンパイルすると問題点がエラーとなって見つけられる。
リスト 5.24: 5-26.cpp

12
3// g++ -Wall -ansi -std=c++11 -o 5-26 5-26.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

const int STRBUF = 10;(次のページに続く)88

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
8
9

int

strlength(const char str[]);10
11

int main(void)

12

{
char

13

a[STRBUF] = "abcdefg";

14

cout << a << " " << strlength(a) << endl;

15
16

exit(EXIT_SUCCESS);

17
18

}

19
20

int

21

{

strlength(const char str[])22for (int i = 0; i < STRBUF; i++) {23

if (str[i] = 0) return(i);

24

}

25

return(-99);

26}$ g++ -o 5-26 5-26.cpp
5-26.cpp: In function

int strlength(const char*) :5-26.cpp:24:22: error: assignment of read-only location*(str + ((sizetype)i))if (str[i] = 0) return(i);^比較の == をタイプミスで = としていたため正しく動作しなかった。ただしこの代入の操作は文法的には問題ないためコンパイルは通っていた。const を付加することで想定外の代入を検出することができた。
変更されるべきでない変数には「すべて」 const を付けるとよい。それにより、• 視覚的にそれが定数であることがわかる。
• 単純なミスによる誤動作を早い段階で検出、修正できる可能性が高まる。
• ミスとは別に、関数の呼び出し時の変数の扱いを限定できる。

5.6 同じ名前の関数、関数のオーバーロード
C 言語で次の３個の関数を作成して main() から呼び出すプログラムを作成してみよう。関数名の最初は"cube" (3 乗の意味) を付けることにする。
• int 型の引数１個を受け取って、3 乗した int の値を返り値とする。
• ﬂoat 型の引数１個を受け取って、3 乗した ﬂoat の値を返り値とする。
• double 型の引数１個を受け取って、3 乗した double の値を返り値とする。

5.6. 同じ名前の関数、関数のオーバーロード

89

Robot Programming II Documentation, リリース 1.0
C 言語では同じ名前の関数を２個以上プログラム内に作成することはできない。よってそれぞれの関数名は何か違う名前を付けて区別する必要がある。
リスト 5.25: 5-27.c
1
2
3// gcc -Wall -ansi -std=c99 -o 5-27 5-27.c#include <stdio.h>#include <stdlib.h>

4
5

int

6

float

cubefloat(float n);

7

doublecubedouble(double n);cubeint(int n);

89

int main(void)

10

{

11

int

12

float

f = 5.0;

13

doubled = 5.0;

i = 5;

14
15

printf("%d\n", cubeint(i));

16

printf("%f\n", cubefloat(f));

17

printf("%f\n", cubedouble(d));

18exit(EXIT_SUCCESS);

19
20

}

21
22

int

23

{
printf("int\n");24

return(n * n * n);25
26

cubeint(int n)

}

27
28

float

29

{

cubefloat(float n)

30

printf("float\n");31

return(n * n * n);

32

}

33
34

double35{
printf("double\n");36

return(n * n * n);37
38

cubedouble(double n)}39$ ./5-27(次のページに続く)90

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

int
125
float
125.000000
double125.000000
3 個の関数は引数や返り値の型は異なるが、処理の内容は同じなので、同じ名前を付けられると見た時にわかりやすい。例えばすべて cube() という名前にできれば変数の型によって名前を考えたり区別する必要がなくなる。C++ ではこれが可能で、引数の型、個数、並びの組み合わせが異なれば同じ名前にしても区別して呼び出すことができる。これを 関数のオーバーロード と言う。C++ では次のように書ける。
★練習コンパイル、実行してみよう。
リスト 5.26: 5-28.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-28 5-28.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

int

8

float

cube(float n);

9doublecube(double n);cube(int n);

1011

int main(void)

12

{

13

int

14

float

f = 5.0;15

doubled = 5.0;

i = 5;

16
17

cout << cube(i) << endl;

18

cout << cube(f) << endl;

19

cout << cube(d) << endl;

20

exit(EXIT_SUCCESS);

21
22

}

23
24

int

25

{

cube(int n)

26

cout << "int" << endl;

27

return(n * n * n);28}(次のページに続く)

5.6. 同じ名前の関数、関数のオーバーロード

91

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
29
30

float

31

{

cube(float n)

32

cout << "float" << endl;

33

return(n * n * n);

34

}

35
36

double37{

cube(double n)38

cout << "double" << endl;

39

return(n * n * n);40}$ ./5-28
int
125
float
125
double125
引数の型、個数、並びの組み合わせが異なれば別の関数として定義可能という条件を利用すると、次のよ
うな関数も定義できる。
リスト 5.27: 5-29.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-29 5-29.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7
8
9
10
11
12

void

f1(int a, int b, int c);// int
void
void
void
void

f1(int a, int b, int c); // 引数が同じで返り値だけ異なるのは不可
f1(int a, int b, float c);
f1(int a, float b, int c);
f1(int a, float b, float c);
f1(int a, float b, double c);

13
14

int main(void)

15

{

16

int

17

float

f = 5.0;18

doubled = 5.0;

i = 5;

19(次のページに続く)92

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
20

f1(i, i, i);21f1(i, i, f);22f1(i, f, i);23f1(i, f, f);24

f1(i, f, d);25
26

f1(1, 1, 1);27

f1(1, 1, (float)2.0);28

f1(1, (float)2.0, 1);29

f1(1, (float)2.0, (float)2.0);30

f1(1, (float)2.0, 2.0); // 実数はそのままでは double とみなされる

31

exit(EXIT_SUCCESS);

32
33

}

34
35

void

36

{
cout << "int int int" << endl;

37
38

f1(int a, int b, int c)

}

39
40

void

41

{
cout << "int int float" << endl;

4243

f1(int a, int b, float c)

}

44
45

void

46

{
cout << "int float int" << endl;

47
48

f1(int a, float b, int c)

}

49
50

void

51

{
cout << "int float float" << endl;

52
53

f1(int a, float b, float c)

}

54
55

void

56

{
cout << "int float double" << endl;

57
58

f1(int a, float b, double c)}$ ./5-29
int int int
int int float
int float int(次のページに続く)

5.6. 同じ名前の関数、関数のオーバーロード

93

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

int float float
int float double
int int int
int int float
int float int
int float float
int float double
2 番目のコメントにしてある関数はエラーとなる。引数が同じで返り値だけ異なるのは、関数呼び出し時にどれを選んでよいか決められないためである。

5.7 関数テンプレート
前節では、C++では引数が異なれば同じ名前の関数を 2 個以上定義できることを示した。ここでは異なる引数で処理の内容が同じ場合に、1 回の記述でまとめて関数を定義することができる 関数テンプレート の機能を説明する。

5-28.cpp では、異なる型の引数に対して同じ処理をする関数が同じ名前で定義できることを示した。ただし、その場合は関数の実体を定義したい個数分だけ繰り返して記述する必要がある。関数テンプレートを利用すると、この記述を 1 回で済ませることができる。5-28.cpp を関数テンプレートを利用して書き直すと次の 5-30.cpp のように書ける。
★練習コンパイル、実行してみよう。
リスト 5.28: 5-30.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-30 5-30.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

template <typename TYPE>

8

TYPE

cube(TYPE n);

910

int main(void)

11

{

12

int

13

float

f = 5.0;

14

doubled = 5.0;

i = 5;

15

cout << cube(i) << endl;
cout << cube(f) << endl;

16
17(次のページに続く)

94

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

cout << cube(d) << endl;

18
19

exit(EXIT_SUCCESS);

20
21

}

22
23

template <typename TYPE>

24

TYPE

25

{
return(n * n * n);2627

cube(TYPE n)}$ ./5-30
125
125
125
上記の TYPE は、 テンプレート引数 , テンプレート仮引数 と呼ばれる。変数の型を表す仮の名前で、実際に呼び出される時に渡される引数の型で置き換えられる。例えば、int の引数で呼び出すと、その時点
で TYPE を int にした関数の実体が生成され実行される。関数の実体は必要になったら生成されるので、

ﬂoat で呼び出されることがなければ ﬂoat を処理する関数の実体は生成されない。名前として使われている
TYPE は固定されておらず、任意の文字列にすることができる。
テンプレート引数の型は、1 種類である必要はなく複数個定義することができる。次の 5-31.cpp では、TYPE1,

TYPE2 で 2 種類の型を扱うことができる。このテンプレート引数の条件は、a, b は必ず同じ型で、c は何でもよい (TYPE1 と同じでもよいし、異なってもよい) となる。
リスト 5.29: 5-31.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 5-31 5-31.cpp#include <iostream>#include <cstdlib>

4
5

using namespace std;

6
7

template <typename TYPE1, typename TYPE2>

8

void

f1(TYPE1 a, TYPE1 b, TYPE2 c); // a, b は同じ型、c は何でもよい

910

int main(void)

11

{

12

int

13

float

i = 5;
f = 5.0;14
15

f1(i, i, i); // これらの組み合わせは通る

16

f1(i, i, f);17

f1(f, f, i);(次のページに続く)

5.7. 関数テンプレート

95

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

f1(f, f, f);

18
19

//

20
21
22

//

23
24
25

//

f1(i, f, i); // そのままでは a, b の型が異なるので不可
f1<float>(i, f, i); // これで TYPE1 を float として呼び出すことが可能
f1<int, int>(i, f, i); // これで TYPE1, TYPE2 は int として呼び出す
f1(f, i, f);f1(1 , 1 , 2.0);
f1(2.0, 2.0, 1 );f1(1 , 2.0, 1 ); // a, b の型が異なるので不可26

exit(EXIT_SUCCESS);

27
28

}

29
30

template <typename TYPE1, typename TYPE2>

31

void

32

{
cout << "TYPE1 TYPE1 TYPE2" << endl;

33
34

f1(TYPE1 a, TYPE1 b, TYPE2 c)}$ ./5-31
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
TYPE1 TYPE1 TYPE2
20 行目の呼び出しは、そのままでは a, b の型が異なるのでエラーとなる。このような場合は、 テンプレー
ト実引数 という記述で型を明示することで対処することができる。21 行目の関数名の直後にある <ﬂoat>
がそれで、この指示により TYPE1 を ﬂoat として呼び出すことになる。型を明示しないと矛盾が生じる場
合にテンプレート実引数を指定する。

5.8 まとめ
この章の目標• C++ の新しい入出力の記述法がわかる。
• 名前空間の概念を理解して使い方がわかる。
• 参照とポインタの機能の違いが理解できる。
• 値渡し、アドレス渡し、参照渡しの違いがわかり適切に使い分けることができる。
• 動的なメモリ確保の記述法がわかり適切に使うことができる。

96

第 5 章 C++の様々な機能

Robot Programming II Documentation, リリース 1.0• const と #deﬁne の機能の違いが理解できる、適切に使い分けることができる。。
• 同じ名前の関数がどのような場合に定義可能か理解できる。
• 関数テンプレートの記述の仕方を理解して適切に使うことができる。
練習問題

1. cin, cout を使って整数、実数、文字列を入出力するプログラムを作成しなさい。using は使用しないで作成する。(e5-1.cpp)

2.

1. のプログラムに対して using を使って std:: を省略できるようにしなさい。(e5-2.cpp)

3. 2. のプログラム全体が名前空間 abc に属するように定義しなさい。 → 問題を次のように変更する。
2. のプログラムの実行する内容を関数 void f(void) として名前空間 abc に属するように定義しなさい。
main() から呼び出しなさい。(e5-3.cpp)
4.

3. のプログラムに対して using を使って abc:: を省略できるようにしなさい。(e5-4.cpp)

5. アドレス渡しで２個の引数の値を交換する関数 void swap1(int ＊ a, int ＊ b) を作成しなさい。main()
から呼び出して動作を確認しなさい。(e5-5.cpp)

6. 参照渡しで２個の引数の値を交換する関数 void swap2(int &a, int &b) を作成しなさい。main() から呼び出して動作を確認しなさい。(e5-6.cpp)

7. 5-19.cpp を使って、new で確保できる int の配列の上限サイズのおおよその値を実験的に求めなさい。
配列の内容を表示すると遅くなるので、cout の行はコメントアウトしなさい。PC に搭載している

RAM の容量に依存するが、数億から数十億になる。(e5-7.cpp)
8. new で適当な同じ大きさ n の int の配列を２個確保して、それぞれを n 次元のベクトルとみなして、内積を計算、表示するプログラムを作成しなさい。(e5-8.cpp)

9. char src[] の文字列を逆順に並べ替えて char dst[] にコピーする関数 void reverse(char src[], char dst[])を作成しなさい。const の指定を適切に付加しなさい。main() から呼び出して動作を確認しなさい。
実行例: "abc12345678" -> "87654321cba" (e5-9.cpp)

10. int, ﬂoat, double の各型で、２個の引数の値を交換する 3 個の同名の関数 void myswap(int &a, int &b)等を作成しなさい。main() から呼び出して動作を確認しなさい。(e5-10.cpp)

11. 関数テンプレートの機能を利用して、２個の引数の値を交換する関数 void myswap(TYPE &a, TYPE&b) を作成しなさい。main() から呼び出して動作を確認しなさい。(e5-11.cpp)

5.8. まとめ

97

99

第 6 章 オブジェクト指向 1、class

6.1 オブジェクト指向とは
オブジェクト指向 (Object Oriented) とは、操作や処理の対象を モノとして表現し、そのモノに対して指示を送ることで操作や処理を行わせる、という考え方である。オブジェクト指向はこれまでに、以下に示す様々な分野で適用されている。

• オブジェクト指向プログラミング• オブジェクトデータベース、関係データベース• オブジェクト指向分析設計• オブジェクト指向モデリング• オブジェクト指向オペレーティングシステム• オブジェクト指向ソフトウェア工学• オブジェクト指向ユーザーインターフェース• オブジェクト指向プロジェクト管理
ここではプログラミング分野における適用に限定する。特定のプログラミング言語に依存しない一般的なプログラムの構成要素は、 データ と コード となる。C 言語における呼び方では変数と関数となる。

• データ（C 言語では変数、オブジェクト指向では プロパティ と呼ばれる）• コード（C 言語では関数、オブジェクト指向では メソッド と呼ばれる）データは処理対象、コードは処理の内容、プログラムをを表すと言える。

C 言語は「手続き型プログラミング言語」に分類される。変数 (データ) と関数 (コード) は別のものとして表現し、言語仕様においては両者に直接の関連はない。C 言語で記述されたプログラムの動作は、必要な計算、変換、処理を行う関数を作成しておいて、そこに処理対象の変数を送って処理をさせる、と一般化できる。

C++ は「オブジェクト指向プログラミング言語」に分類される。変数 (データ) と関数 (コード) を、クラス
class と呼ばれる表現の仕方で、一体化して記述する。この class がオブジェクト指向を言語仕様として実現する。C++ で記述されたプログラムの動作は、変数と関数を class の枠組みで一体化して作成したオブジェ
クトがあって、そのオブジェクトに処理の内容を表すメッセージを送って処理をさせる、と一般化できる。
オブジェクト指向の世界では、特有の概念を表すいくつかの用語があるので以下に示す。
クラス オブジェクト指向の考え方で表現するモノの雛形、設計図、定義。クラスの段階では実体はまだない、よって操作できない。int, ﬂoat などの変数の型や構造体の定義に相当する。

Robot Programming II Documentation, リリース 1.0
インスタンス (instance) クラスに基づいて作成した変数の実体。インスタンスを作成することで操作ができるようになる。インスタンスを作成することを インスタンス化と呼ぶ。int a; の a は作成されたイ
ンスタンスと言える。
オブジェクト 処理対象のモノ。実体があるインスタンスはオブジェクトである。クラスは実体がないのでオブジェクトとしないことが多い。雛形や設計図というモノとされることもある。
オブジェクト指向プログラミングでは、次に示す原理、機能が重要とされている。
カプセル化 (encapsulation) クラスの外部で必要とされない変数や関数は公開しないで、クラス内部で閉じておくこと。カプセル化を進めることで、壊れにくい、安全なプログラムを作成できる。
インヘリタンス (inheritance)、継承 複数のクラスが階層的に定義され、上位概念を表すクラスで定義される内容を、下位クラスが受け継ぐこと。例として、動物クラスが「生きている」
、
「動く」
、
「ものを食
べる」という性質を持ち、哺乳類クラスが「足がある」、「子供を生む」という性質を持つとする。それらの下位クラスとして人間クラスを定義すると、前述の２クラスの性質は人間クラスで改めて定義しなくても、継承の機能で自動的に定義される。
ポリモーフィズム (polymorphism)、多態性 同じ名前、見た目であっても、処理対象の変数が変わるとその処理の内容が変化すること。例として、前章のサンプルプログラム 5-27.c, 5-28.cpp では、引数の 3乗を返す関数 cube() を作成した。C 言語では多態性を備えていないため、引数の型が変わると関数名も変える必要があった。C++ は多態性を備えるため、同一の関数名 cube() で異なる型の引数を扱うことができる。

6.2 C 言語におけるデータとコードの表現
C 言語で変数、関数がどのように記述されるかを示す例として、人間を表す構造体 human を定義して、メンバ変数への代入と表示を関数で実装してみる。class は構造体と近い性質があるので、データの例として構造体を取り上げる。
★練習コンパイルして実行してみよう。
リスト 6.1: 6-1.cpp
12
34// gcc -Wall -ansi -std=c99 -o 6-1 6-1.c#include<stdio.h>#include<stdlib.h>#include<string.h>5
6#defineARYSIZE#define#defineMALE
FEMALE#define#define#define

JAPAN
US
UK

64

7
8
9

0
1

10
11
12
13

0
1
2
(次のページに続く)100

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
14
15#define#define

FRANCE
ITALY

3
4

16
17

struct human {

18

char

name[ARYSIZE];
nation; // nationality 国籍

19

int

20

int

sex;

21

int

age;

22

float

height; // meterfloat

weight; // kilo gram

23
24// MALE or FEMALE

};

25
26

typedef struct human

Human;

27
28

void human_set(Human *p, char name[], int n, int s, int a, float h, float w);

29

void human_print(Human a); // 変数と関数に言語仕様上の関係は存在しない

30
31
32

int main(void){

33

struct humana; // この行、次の行、どちらの書き方でもよい

34

Human

b;

35
36

human_print(a); // 自動で初期化はされない

37

human_print(b);3839strcpy(a.name, "Chubu Taro"); // 構造体はメンバ変数に自由にアクセス可能

40

a.nation = JAPAN;

41

a.sex = MALE;

42a.age = 20;

43a.height = 1.75;

44

a.weight = 62.0;

45

human_print(a);4647human_set(&b, "Chubu Hanako", JAPAN, FEMALE, 19, 1.65, 52.0);

48

human_print(b);49exit(EXIT_SUCCESS);

50
51

}

52
53

void human_set(Human *p, char name[], int n, int s, int a, float h, float w)

54

{

55

strcpy(p -> name, name);57

p -> nation = n;p -> sex = s;58

p -> age = a;56

(次のページに続く)

6.2. C 言語におけるデータとコードの表現

101Robot Programming II Documentation, リリース 1.0(前のページからの続き)

p -> height = h;59

p -> weight = w;

60
61

}

62
63

void human_print(Human a)

64{
printf("name:%s65

sex:%dage:%dheight:%.2f

weight:%.2f\n",a.name, a.nation, a.sex, a.age, a.height, a.weight);

66
67

nation:%d}$ gcc -o 6-1 6-1.c$ ./6-1
name:
name:

t3

nation:0 sex:0 age:0 height:0.00 weight:0.00
nation:-457586160 sex:21851 age:-457587248 height:0.00 weight:0.00

name:Chubu Taro nation:0 sex:0 age:20 height:1.75 weight:60.00
name:Chubu Hanako nation:0 sex:1 age:19 height:1.65 weight:50.00
C 言語には、クラス、インスタンス、オブジェクトという概念はないが、プログラム中の処理をそれらに対応させるとすると、• 17 行目からの構造体の宣言はクラスの宣言に相当する。
• 33, 34 行目の a, b の宣言は、インスタンスを作成したことに相当する。また a, b というオブジェクトを生成したとも言える。
また、C 言語の言語仕様から来る特徴、制約として以下がある。

• 構造体のメンバ変数はプログラム内のどこでも参照、代入できる。
• 構造体 human と、関数 human_set(), human_print() は関係ない。関数名に human を付けて関連性がわかるようにしてあるが、そうしておかないと何をする関数かわからない。

• 関数 human_set(), human_print() には、引数として変数を渡すことで、それを処理させることができる。

6.3 C++ におけるデータとコードの表現
前節で示した 6-1.c と同様の機能を C++ で class を利用して作成すると次に示す 6-2.cpp になる。
★練習コンパイルして実行してみよう。C 言語との違いを比較してみよう。
リスト 6.2: 6-2.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-2 6-2.cpp#include<iostream>#include<cstdlib>4(次のページに続く)

102

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

const int

ARYSIZE = 64;

7

const int

MALE

= 0;

8

const int

FEMALE

= 1;

5
6

9
10

const int

JAPAN

= 0;

11

const int

US

= 1;

12

const int

UK

= 2;

13

const int

FRANCE= 3;

14const int

ITALY

= 4;

15
16

using namespace std;

17
18

class human {19

private:20

public: // 練習問題があるので最初はこのまま
string

21

name;

23

int
int

24

int

age;

25

float

height; // meter

26

float

weight; // kilo gram

22

27

public:void set(string name, int n, int s, int a, float h, float w);

28

void print(); // class のメンバ関数としてデータと関連付けができる29
30

nation; // nationality 国籍
sex;// MALE or FEMALE

};

31
32

int main(void)

33

{

34

human

a, b; // class の場合はこのように書ける

35
36

a.print(); // 自動で初期化はされない

37

b.print();

38
39
40
41
42
43
44
45
46
47
48
49

/*
// class では公開されているメンバ変数しかアクセスできない// class 外からは書き換えることはできない
a.name = "Chubu Taro";
a.nation = JAPAN;
a.sex = MALE;
a.age = 20;
a.height = 1.75;
a.weight = 60.0;*/
a.set("Chubu Taro", JAPAN, MALE, 20, 1.75, 62.0);
a.print();(次のページに続く)

6.3. C++ におけるデータとコードの表現

103

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
50
51

b.set("Chubu Hanako", JAPAN, FEMALE, 19, 1.65, 52.0);52b.print();53

exit(EXIT_SUCCESS);

54
55

}

56
57

void human::set(string str, int n, int s, int a, float h, float w)

58

{

59

name = str;

60

nation = n;61

sex = s;62

age = a;63

height = h;64weight = w;

65

}

66
67
68

void human::print(){
cout << "name:" << name

69
70

<< "

nation:" << nation

71

<< "

sex:" << sex

72

<< "

age:" << age

73

<< "

height:" << height<< "weight:" << weight << endl;

74
75

}

$ g++ -o 6-2 6-2.cpp$ ./6-2
name:nation:1

name:nation:-1893738080

name:Chubu Taro

sex:0

age:-450580092

nation:0

sex:32744

name:Chubu Hanako

sex:0

nation:0

height:3.09519e-41
age:0

age:20

sex:1

height:0

weight:-4.86093e+22

height:1.75

age:19weight:0

weight:60

height:1.65

weight:50• class human にはメンバ変数の他に、 メンバ関数 として set(), print() が定義されている。C 言語では構造体とは別に、human_set(), human_print() として定義されていたが、 class では変数と一緒に関数を定義できる。メンバ関数はそのオブジェクトの処理を専用に行う関数である。

• メンバ関数の呼び出し、実行は、a.set() のように構造体のメンバ変数の参照と同じ形式でオブジェク
トを左に指定する。これでオブジェクト a が持つ関数 set() が呼び出される。これはオブジェクト aに対して、set() という処理を行うように指示していると考えられる。

• メンバ関数内でのメンバ変数の参照は変数名だけ書けばよい。これでそのオブジェクトの変数を参照、代入できる。

104

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0

6.4 アクセス指定子 public, private
C++ において、カプセル化を実現するのが、class の定義中に書かれるアクセス指定子 public と private である。public を指定するとその後に記述されるメンバ変数やメンバ関数はオブジェクト外部に公開され、参照や代入が可能となる。private は非公開を指示してオブジェクト外部からは参照できなくなる。public とprivate ともに、次に別の指定が現れるまでずっと有効になる。また、public, private は class の定義中に何回現れてもよい。
カプセル化を確実に実現するために、メンバ変数やメンバ関数はなるべく private で指定して、public で指定するものは厳選するとよい。それにより外部でそのオブジェクトを扱うプログラムは注意すべき事柄が少なくなり間違いを減らすことができる。

6.5 メンバ関数
カプセル化を実現するために、よく使われる手法がメンバ変数は private で宣言しておいて、その変数を操作するメンバ関数を public で宣言してメンバ変数にアクセスする窓口をメンバ関数に限定するものである。
次のプログラムは class human のメンバ関数で、性別を操作する関数の例である。
★練習コンパイルして実行してみよう。
リスト 6.3: 6-3.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-3 6-3.cpp#include<iostream>#include<cstdlib>

4
5

const int

SUCCESS = 0;

6

const int

ERROR

= 1;

8

const int

MALE

= 0;

9

const int

FEMALE

= 1;

7

10
11

// 途中略12
13

using namespace std;

14
15

class human {16

private:17

string

name;

18

int

19

int

20

int

age;

21

float

height; // meter

22

float

weight; // kilo gramnation; // nationality 国籍
sex;// MALE or FEMALE(次のページに続く)

6.4. アクセス指定子 public, private

105

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
23

public:24

void set(string name, int n, int s, int a, float h, float w);

25

void print();

26

int

setsex(int s); // 性別をセットする

27

int

getsex(void);28// 性別の値を読み出す

};

29
30

int main(void)

31

{
human

32

a;

33
34

a.setsex(MALE);

35

cout << a.getsex() << endl;

36

exit(EXIT_SUCCESS);

37
38

}

39
40
41
42
43

void human::set(string str, int n, int s, int a, float h, float w)
{

//
}

略

44
45

void human::print()

46

{

47
48

//
}

略

49
50

int human::setsex(int s)

51{
if (MALE == s || FEMALE == s) { // 値の確認

52

sex = s;53

return(SUCCESS);

54
55

}

56

else {
return(ERROR);

57

}

58
59

}

60
61

int human::getsex(void)

62{
return(sex);

63
64

}

$ ./6-3
0106

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0
setsex() は性別を設定し、 getsex() は性別の設定値を読み出す。メンバ変数 sex は private で宣言されている
ので、sex にアクセスするには setsex(), getsex() を使うしかない。setsex() では引数の値の確認をすることで間違った値が代入されることを防いでいる。
メンバ関数経由の変数アクセスに限定することには次のメリットがある。

• プログラム内のどこかで知らないうちに書き換えられることを防ぐことができる。
• 代入される値を事前に確認することで、間違った値の代入を防ぐことができる。

6.6 コンストラクタ
ここまではメンバ変数に値を代入する関数の例として、human::set() を示してきた。メンバ変数への代入を行う一般的な処理にはこの関数を使える。また変数の初期化にも使うことができる。
ただし、初期化に関しては C++ の言語仕様として class に専用の仕組みが用意されているので、それを使うのがよい。独自に作成した初期化の機能や関数は使わないことが推奨される。
用意されている専用の仕組みとは、 コンストラクタ (Constructor) と呼ばれる初期化専用の関数で、コンストラクタに関して決められている書式に合わせて記述しておくと、オブジェクトが作成された直後に自動的に呼び出される。次のプログラムにはコンストラクタが記述されている。
★練習コンパイルして実行してみよう。コンストラクタ内部の処理を変えてみて結果を見てみよう。
リスト 6.4: 6-4.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-4 6-4.cpp#include<iostream>#include<cstdlib>

4
5

const int

SUCCESS = 0;

6

const int

ERROR

= 1;

8

const int

MALE

= 0;

9

const int

FEMALE

= 1;

7

10
11

// 途中略12
13

using namespace std;

14
15

class human {16

private:17

string

name;

18

int

19

int

20

int

age;

21

float

height; // meternation; // nationality 国籍
sex;// MALE or FEMALE(次のページに続く)

6.6. コンストラクタ

107

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

float

22
23

weight; // kilo gram

public:24

human();

25

human(string str, int n = 0, int s = 0, int a = 0,
float h = 0, float w = 0);2627void set(string name, int n, int s, int a, float h, float w);

28

void print();

29

int

setsex(int s);

30

int

getsex(void);

31

};

32
33

int main(void)

34

{

35

human

a;

36

human

b("bbb", 1, 1, 20, 160, 50);

37

human

c("ccc", 0, 0);38
39a.print();

40

b.print();

41

c.print();42

exit(EXIT_SUCCESS);

43
44

}

45
46

human::human()47{
cout << "Constructor was called." << endl;

48

name = "noname";

49
50

}

51
52

human::human(string str, int n, int s, int a, float h, float w)

53

{

54

cout << "Constructor2 was called." << endl;

55

name = str;

56

nation = n;57

sex = s;58

age = a;59

height = h;60weight = w;

61

}

62
63

void human::set(string str, int n, int s, int a, float h, float w)

65

{
//

66

}

64

略
(次のページに続く)

108

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
67
68

void human::print()

69{
cout << "name:" << name

70
71

<< "

nation:" << nation

72

<< "

sex:" << sex

73

<< "

age:" << age

74

<< "

height:" << height

75

<< "

weight:" << weight << endl;

76

}

77
78
79
80
81

int human::setsex(int s)
{
//
}

略

82
83

int human::getsex(void)84{
return(sex);

85
86

}
24, 25, 26 行目にプロトタイプ宣言が、46--61 行目に関数の実体がある。コンストラクタは、• class 名と同じ名前の関数とする。この場合は、human::human() となる。
• 返り値はなく、void も記述しない。
• 引数はなくてもよい。指定することもできる。
• 明示的に呼び出さなくても自動的に実行される。
• コンストラクタを作成しない場合は、何もしないコンストラクタが内部で自動的に生成、実行される。
この例では、関数のオーバーロードの機能を使って、引数がある場合と、ない場合をそれぞれ記述している。また、引数がある場合については、 デフォルト引数 という機能を使って、値を省略した場合に自動的にデフォルト値が渡されるようにしている。次に示すのは、このプログラムを実行した時の結果である。
プログラムでは 35--37 行目でオブジェクトを生成して、その後 39--41 行目で設定値を表示している。結果を見ると、 print() の表示の前にコンストラクタが実行された時のメッセージが表示されている。

$ ./6-4Constructor was called.
Constructor2 was called.
Constructor2 was called.
name:noname

nation:3379

sex:327

age:334

height:4.58e-41

name:bbb

nation:1

sex:1

age:20height:160

name:ccc

nation:0

sex:0

age:0

height:0

weight:6.20e-27

weight:50

weight:0

次のプログラムはオブジェクトの代入が可能であることを示している。a, b を生成した後で、39 行目で b= a; として代入している。オブジェクト間の代入の操作は標準的な処理が用意されていて、メンバ変数の6.6. コンストラクタ

109

Robot Programming II Documentation, リリース 1.0
値が代入される。単一のメンバ変数は、この処理でほとんど問題ないが、配列やポインタでは問題が生じる。その場合は専用の代入処理を作成する必要がある (後述)。
★練習コンパイルして実行してみよう。
リスト 6.5: 6-5.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-5 6-5.cpp#include<iostream>#include<cstdlib>

4
5

const int

SUCCESS = 0;

6

const int

ERROR

= 1;

8

const int

MALE

= 0;

9

const int

FEMALE

= 1;

7

10
11

// 途中略12
13

using namespace std;

14
15

class human {16

private:17

string

18

int

19

int

sex;

20

int

age;

21

float

height; // meterfloat

weight; // kilo gram

22
23

name;
nation; // nationality 国籍// MALE or FEMALE

public:24

human();

25

human(string str, int n = 0, int s = 0, int a = 0,
float h = 0, float w = 0);2627void set(string name, int n, int s, int a, float h, float w);

28

void print();

29

int

setsex(int s);

int

getsex(void);

30
31

};

32
33

int main(void)

34

{

35

human

a("aaa");

36

human

b;

37

a.print();38(次のページに続く)

110

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
39

b = a;

40b.print();41

exit(EXIT_SUCCESS);

42
43

}

44
45

human::human()46{
cout << "Constructor was called." << endl;

47

name = "noname";

48
49

}

50
51

human::human(string str, int n, int s, int a, float h, float w)

52

{

53

cout << "Constructor2 was called." << endl;

54

name = str;
nation = n;55

57

sex = s;age = a;58

height = h;56weight = w;

59
60

}

61
62

void human::set(string str, int n, int s, int a, float h, float w)

63

{

64
65

//
}

略

66
67

void human::print()

68{
cout << "name:" << name

69
70

<< "

nation:" << nation

71

<< "

sex:" << sex

72

<< "

age:" << age

73

<< "

height:" << height<< "weight:" << weight << endl;

74
75

}

76
77
78
79
80

int human::setsex(int s)
{
//
}

略

81
82

int human::getsex(void)

83{(次のページに続く)

6.6. コンストラクタ

111

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

return(sex);

84
85

}
結果の出力では、a, b が同じ内容を保持していることがわかる。

$ ./6-5Constructor2 was called.
Constructor was called.
name:aaa

nation:0

sex:0

age:0

height:0

weight:0

name:aaa

nation:0

sex:0

age:0

height:0

weight:0

6.7 コンストラクタ（動的メモリ確保）※
動的にメモリを確保する new を使うと、個々のオブジェクトで異なるサイズの配列を持つことができる。
新たなメンバ変数として、25, 26 行に配列のサイズとアドレスを保持するポインタを追加している。コン
ストラクタでは配列のサイズに合わせて new で領域を確保している。サイズが 0 以下の場合はアドレスを

NULL にして確保しない別処理としている。print() には配列の内容を表示する処理を追加している。
★練習コンパイルして実行してみよう。確保する配列のサイズを変えて動作を確認しよう。
リスト 6.6: 6-6.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-6 6-6.cpp#include<iostream>#include<cstdlib>

4
5

const int

SUCCESS = 0;

6

const int

ERROR

= 1;

8

const int

MALE

= 0;

9

const int

FEMALE= 1;

const int

ARYSIZE = 10;

7

10
11
12
13

// 途中略14
15

using namespace std;

16
17

class human {18

private:19

string

20

int

21

int

name;
nation; // nationality 国籍
sex;// MALE or FEMALE(次のページに続く)112

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
22

int

age;

23

float

height; // meter

24

float

weight; // kilo gram

25

int

arysize;

26

int

*ary;

27

public:28

human();

29

human(string str, int n = 0, int s = 0, int a = 0,30

float h = 0, float w = 0, int as = 0);31void set(string name, int n, int s, int a, float h, float w);

32

void print();

33

int

setsex(int s);

int

getsex(void);

34
35

};

36
37

int main(void)

38

{

39

human

40

a.print();a("aaa");

41
42

human

43

b.print();b("bbb", 1, 1, 0, 0, 0, 10);

44
45

human

46

c.print();c("ccc", 1, 1, 0, 0, 0, 20);47exit(EXIT_SUCCESS);

48
49

}

50
51

human::human()

52

{

53

cout << "Constructor was called." << endl;

54

name = "noname";55

arysize = ARYSIZE;

56
57

ary = new int[ARYSIZE];

58

for (int i = 0; i < arysize; i++) ary[i] = i;

59

}

60
61

human::human(string str, int n, int s, int a, float h, float w, int as)

62

{

63

cout << "Constructor2 was called." << endl;

65

name = str;
nation = n;66

sex = s;64

(次のページに続く)

6.7. コンストラクタ（動的メモリ確保）※

113

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
67

age = a;68

height = h;69weight = w;

70

arysize = as;71

ary = NULL;72

if (0 < arysize) {73

ary = new int[arysize];74

for (int i = 0; i < arysize; i++) ary[i] = i;

75

}

76

else {

77

arysize = 0;78ary = NULL;
}

79
80

}

81
82

void human::set(string str, int n, int s, int a, float h, float w)

83

{

84
85

//
}

略

86
87

void human::print()

88{
cout << "name:" << name

89
90

<< "

nation:" << nation

91

<< "

sex:" << sex

92

<< "

age:" << age

93

<< "

height:" << height<< "weight:" << weight << "94

address:" << ary << endl;

if (0 < arysize) {95
96

for (int i = 0; i < arysize; i++) cout << ary[i] << " ";97

cout << endl;
}

98
99

}

100
101

int human::setsex(int s)

102

{

103
104

//
}

略

105
106

int human::getsex(void)107{
return(sex);

108
109

}
次に示す実行結果では、異なるサイズの配列の内容が表示されている。

114

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0$ ./6-6
Constructor2 was called.
name:aaa

nation:0

sex:0

age:0

height:0

weight:0

address:0

age:0

height:0

weight:0

address:0x55f0c3990280

age:0

height:0

weight:0

address:0x55f0c39902b0

Constructor2 was called.
name:bbb

nation:1

sex:1

0 1 2 3 4 5 6 7 8 9Constructor2 was called.
name:ccc

nation:1

sex:1

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
ここまでで、動的に領域を確保する配列を持つオブジェクトの生成と操作まで実現できた。しかし、動的に確保した領域やポインタが含まれると、まだ次のことができない。

• 確保した領域を解放する処理がない。
• 関数に渡す仮引数や、初期化時に渡す引数が正しく処理できない。
• 代入の処理が正しくできない。

6.8 デストラクタ
オブジェクト生成時の初期化の処理を行うコンストラクタと対の関係となる、オブジェクトが消滅する時の後片付けの処理を行う デストラクタ (Destructor) がある。コンストラクタと同じように、デストラクタ
はオブジェクトが消滅する時に自動的に呼び出される。

• class 名と同じ名前の関数の前に '~' を付ける。この場合は、human::~human() となる。
• 返り値はなく、void も記述しない。
• 引数はない。
• 明示的に呼び出さなくても自動的に実行される。
• デストラクタを作成しない場合は、何もしないデストラクタが内部で自動的に生成、実行される。
次のプログラムは class human にデストラクタを記述したものである。main() 中の 36, 44 行の {, } は、デストラクタのメッセージを表示するために追加している。
★練習コンパイルして実行してみよう。36, 44 行の {, } を消すとどうなるか確認しよう。
リスト 6.7: 6-7.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-7 6-7.cpp#include<iostream>#include<cstdlib>

4
5

const int

SUCCESS = 0;

6

const int

ERROR

= 1;
(次のページに続く)

6.8. デストラクタ

115

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
7
8

const int

MALE

= 0;

9

const int

FEMALE

= 1;

10
11

// 途中略12
13

using namespace std;

14
15

class human {16

private:17

string

18

int

19

int

sex;

20

int

age;

21

float

height; // meter

22

float

weight; // kilo gram

23

name;
nation; // nationality 国籍// MALE or FEMALE

public:human();
human(string str, int n = 0, int s = 0, int a = 0,24
25

float h = 0, float w = 0);2627~human();28

void set(string name, int n, int s, int a, float h, float w);

29

void print();

30

int

setsex(int s);

int

getsex(void);

31
32

};

33
34

int main(void)

35

{

36

{

37

human

a;

38

human

b("bbb", 1, 1, 20, 160, 50);

39

human

c("ccc", 0, 0);40
41a.print();

42b.print();

43

c.print();

44

}

45

exit(EXIT_SUCCESS);

46
47

}

48
49

human::human()50{
cout << "Constructor was called." << endl;

51(次のページに続く)

116

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

name = "noname";

52
53

}

54
55

human::human(string str, int n, int s, int a, float h, float w)

56

{

57

cout << "Constructor2 was called." << endl;

58

name = str;

59

nation = n;60

sex = s;61

age = a;62

height = h;63weight = w;

64

}

65
66

human::~human()67{
cout << "Destructor was called. " << name << endl;

68
69

}

70
71

void human::set(string str, int n, int s, int a, float h, float w)

72

{

73
74

//
}

略

75
76

void human::print()

77{
cout << "name:" << name

78
79

<< "

nation:" << nation

80

<< "

sex:" << sex

81

<< "

age:" << age

82

<< "

height:" << height<< "weight:" << weight << endl;

83
84

}

85
86
87
88
89

int human::setsex(int s)
{
//
}

略

90
91

int human::getsex(void)92{
return(sex);

93
94

}
実行結果が以下になる。デストラクタはオブジェクトを生成した順とは逆に実行される。すなわち後から

6.8. デストラクタ

117

Robot Programming II Documentation, リリース 1.0
生成されたものが先に破棄される。

$ ./6-7
Constructor was called.
Constructor2 was called.
Constructor2 was called.
name:noname

nation:8938

sex:326

age:8879

height:4.57e-41

name:bbb

nation:1

sex:1

age:20height:160

name:ccc

nation:0

sex:0

age:0

height:0

weight:5.69e-07

weight:50

weight:0

Destructor was called. ccc
Destructor was called. bbb
Destructor was called. noname

6.9 デストラクタ（動的メモリ確保）※
デストラクタでは、通常実行する必要がある処理はほとんどないが、唯一重要な処理として new で確保した領域の解放がある。new で確保していた領域がある場合は、必ず delete で解放しなくてはならない。これを忘れると メモリリーク となり、使えるメモリがリークの度に徐々に少なくなる。次に示すのは new で配列を確保する 6-6.cpp に delete で配列を解放する処理を行うデストラクタを追加したものである。main()
中の 41, 48 行の {, } は、デストラクタのメッセージを表示するために追加している。
★練習コンパイルして実行してみよう。
リスト 6.8: 6-8.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-8 6-8.cpp#include<iostream>#include<cstdlib>

4
5

const int

SUCCESS = 0;

6

const int

ERROR

= 1;

8

const int

MALE

= 0;

9

const int

FEMALE= 1;

const int

ARYSIZE = 10;

7

10
11
12
13

// 途中略14
15

using namespace std;

16
17

class human {18

private:string19name;(次のページに続く)118

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
20

int

21

int

sex;

22

int

age;

23

float

height; // meter

24

float

weight; // kilo gram

25

int

arysize;

26

int

*ary;

27

public:nation; // nationality 国籍// MALE or FEMALE

28

human();

29

human(string str, int n = 0, int s = 0, int a = 0,30

float h = 0, float w = 0, int as = 0);31~human();32

void set(string name, int n, int s, int a, float h, float w);

33

void print();

34

int

setsex(int s);

35

int

getsex(void);

36

};

37
38

int main(void)

39

{
{

40
41

human

42

a.print();

43

human

44

b.print();

45

human

46

c.print();b("bbb", 1, 1, 0, 0, 0, 10);c("ccc", 1, 1, 0, 0, 0, 20);}47exit(EXIT_SUCCESS);

48
49

a("aaa");

}

50
51

human::human()

52

{

53

cout << "Constructor was called." << endl;

54

name = "noname";

55
56

arysize = ARYSIZE;

57

ary = new int[ARYSIZE];

58

for (int i = 0; i < arysize; i++) ary[i] = i;

59

}

60
61

human::human(string str, int n, int s, int a, float h, float w, int as)

62

{

63

cout << "Constructor2 was called." << endl;

64

name = str;
(次のページに続く)

6.9. デストラクタ（動的メモリ確保）※

119

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
65

nation = n;66

sex = s;67

age = a;68

height = h;69weight = w;

70

arysize = as;71

ary = NULL;72

if (0 < arysize) {ary = new int[arysize];73

for (int i = 0; i < arysize; i++) ary[i] = i;

74
75

}

76

else {

77

arysize = 0;78ary = NULL;
}

79
80

}

81
82

human::~human()83{
cout << "Destructor was called. " << name << endl;

84

delete[] ary;

85
86

}

87
88

void human::set(string str, int n, int s, int a, float h, float w)

89

{

90
91

//
}

略

92
93

void human::print()

94{
cout << "name:" << name

95
96

<< "

nation:" << nation

97

<< "

sex:" << sex

98

<< "

age:" << age

99

<< "

height:" << height<< "weight:" << weight << "100address:" << ary << endl;

if (0 < arysize) {

101
102

for (int i = 0; i < arysize; i++) cout << ary[i] << " ";103

cout << endl;
}

104
105

}

106

108

int human::setsex(int s)
{

109

//

107

略
(次のページに続く)120

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
110

}

111
112

int human::getsex(void)

113{
return(sex);

114
115

}

$ ./6-8
Constructor2 was called.
name:aaa

nation:0

sex:0

age:0

height:0

weight:0

address:0

age:0

height:0

weight:0

address:0x559e085e4280

age:0

height:0

weight:0

address:0x559e085e42b0

Constructor2 was called.
name:bbb nation:1 sex:1
0 1 2 3 4 5 6 7 8 9Constructor2 was called.
name:ccc

nation:1

sex:1

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Destructor was called. ccc
Destructor was called. bbb
Destructor was called. aaa

6.10 コピーコンストラクタ
オブジェクトの生成時にはコンストラクタが呼び出されるが、他のオブジェクトの内容をコピーして初期化する場合は、通常のコンストラクタではない別のコンストラクタが呼び出される。これを コピーコンス
トラクタ (Copy Constructor) と呼ぶ。コピーコンストラクタが呼び出されるのは、• オブジェクト作成後の初期化時に他のオブジェクトの内容をコピーするとき• 関数呼び出し時に値渡しで、仮引数にコピーが作成されるときである。
コピーコンストラクタを明示的に指定しない場合は、標準的な処理を行うコピーコンストラクタが自動的に生成され実行される。標準的な処理とは、各メンバ変数に対してコピー元の値を代入する。メンバ変数
として通常の変数や配列のみを含む場合はこれで問題ない。以下の例ではこれまでと同じ class human を扱うが、関係ないメンバ変数や関数は削除してある。
★練習コンパイルして実行してみよう。
リスト 6.9: 6-9a.cpp
1
2
3// g++ -Wall -ansi -std=c++11 -o 6-9a 6-9a.cpp#include<iostream>#include<cstdlib>(次のページに続く)

6.10. コピーコンストラクタ

121

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
4
5

using namespace std;

6
7class human {8

private:9

const static int

10

int

11

public:arysize = 10;
ary[arysize];

12

human();

13~human();14

void print();

15

void setary(int idx, int val);

16

};

17
18

int main(void)

19

{

21

{
human

22

a.print();

20

a;

23
24

human

25

b.print();

b;

26
27

human

28

c.print();c = b;

29
30

cout << endl;

31

b.setary(7, 99);

32

b.print();

33

c.print();34}
exit(EXIT_SUCCESS);

35
36

}

37
38

human::human()

39

{

40

cout << "Constructor was called." << endl;

41

for (int i = 0; i < arysize; i++) ary[i] = i;

42

}

43
44

human::~human()45{
cout << "Destructor was called. " << endl;

46
47

}

48

(次のページに続く)

122

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
49

void human::print()

50

{

51

cout << arysize << "52

if (0 < arysize) {for (int i = 0; i < arysize; i++) cout << ary[i] << " ";53

cout << endl;

54

}

55
56

address:" << ary << endl;

}

57
58

void human::setary(int idx, int val)

59{
ary[idx] = val;

60
61

}
以下が実行結果である。c には b の内容をコピーして初期化している。自動的に生成されたコピーコンス
トラクタが使用されるが、配列の内容が正しくコピーされていることがわかる。その後、b の配列の内容を一部書き換えているが、b, c の配列を表示すると、それぞれ正しい内容となっている。

$ ./6-9a
Constructor was called.
10

address:0x7ffc88f9de60

0 1 2 3 4 5 6 7 8 9Constructor was called.
10

address:0x7ffc88f9de90

0 1 2 3 4 5 6 7 8 910address:0x7ffc88f9dec0

0 1 2 3 4 5 6 7 8 910address:0x7ffc88f9de90

0 1 2 3 4 5 6 99 8 910address:0x7ffc88f9dec0

0 1 2 3 4 5 6 7 8 9Destructor was called.
Destructor was called.
Destructor was called.

6.11 コピーコンストラクタ（動的メモリ確保）※
メンバ変数として動的メモリ確保による配列が含まれると、自動的に生成されるコピーコンストラクタでは問題が生じる。次のプログラムでその具体例を示す。これまでと同じ class human を扱うが、問題点をわかりやすくするため、関係ないメンバ変数や関数は削除してある。
★練習コンパイルして実行してみよう。

6.11. コピーコンストラクタ（動的メモリ確保）※

123

Robot Programming II Documentation, リリース 1.0

リスト 6.10: 6-9b.cpp
1
2
3// g++ -Wall -ansi -std=c++11 -o 6-9b 6-9b.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7class human {8

private:

10

int
int

11

public:9arysize;*ary;

12

human(int as = 0);13~human();14

void print();

15

void setary(int idx, int val);

16

};

17
18

int main(void)

19

{

20

{

21

human

22

a.print();

a;

23
24

human

25

b.print();b(20);

26
27

human

28

c.print();c = b;

29
30

cout << endl;

31

b.setary(7, 99);

32

b.print();

33

c.print();}exit(EXIT_SUCCESS);

34
35
36

}

37
38

human::human(int as)

39

{

40

cout << "Constructor was called." << endl;

41

arysize = as;42ary = NULL;

43

if (0 < arysize) {(次のページに続く)

124

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

ary = new int[arysize];44

for (int i = 0; i < arysize; i++) ary[i] = i;

45
46

}

47

else {
arysize = 0;48

ary = NULL;

49

}

50
51

}

52
53

human::~human()54{
cout << "Destructor was called. " << endl;

55

delete[] ary;

56
57

}

58
59

void human::print()

60

{

61

cout << arysize << "62

if (0 < arysize) {for (int i = 0; i < arysize; i++) cout << ary[i] << " ";63

cout << endl;

64

}

65
66

address:" << ary << endl;

}

67
68

void human::setary(int idx, int val)

69{
ary[idx] = val;

70
71

}
以下が実行結果である。c には b の内容をコピーして初期化している。b の配列の内容を一部書き換えているが、その後 b, c の配列を表示すると、c の配列の要素も同じ値に変わってしまっている。これは初期化の時に b の配列の先頭アドレスがコピーされたため、 c の配列は b と同じ場所を指しているためである。本
来 c の配列は独自の場所を確保する必要があるが、それができていないことがわかる。また b, c のデスト
ラクタによる処理では、同じ領域を２回 delete[] により解放しようとした結果、プログラムが異常終了している。
この、内容ではなく先頭アドレスだけがコピーされる現象が問題点である。標準で用意されるコピーコン
ストラクタではこの現象を防ぐことができないので、動的なメモリ確保の処理を含む場合は、それに対応するコピーコンストラクタを独自に用意する必要がある。

$ ./6-9b
Constructor was called.
0

address:0

Constructor was called.
20

address:0x56182bee7280(次のページに続く)

6.11. コピーコンストラクタ（動的メモリ確保）※

125

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20

address:0x56182bee7280

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20

address:0x56182bee7280

0 1 2 3 4 5 6 99 8 9 10 11 12 13 14 15 16 17 18 19
20

address:0x56182bee7280

0 1 2 3 4 5 6 99 8 9 10 11 12 13 14 15 16 17 18 19
Destructor was called.
Destructor was called.
free(): double free detected in tcache 2
中止 (コアダンプ)
次に示すのは、正しくコピーの処理をするコピーコンストラクタを実装したプログラムである。
★練習コンパイルして実行してみよう。
リスト 6.11: 6-10.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-10 6-10.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7class human {8

private:

10

int
int

11

public:9arysize;*ary;

12

human(int as = 0);13human(const human& org);

14~human();15

void print();

16

void setary(int idx, int val);

17

};

18
19

int main(void)

20

{

21

{

22

human

23

a.print();

a;

24

human

25

b(20);(次のページに続く)

126

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

b.print();

26
27

human

c = b;

humanc.print();

c(b);

32

human

d;

33

d = b;

34

d.print();

28
29

//

30

// b の内容をコピーして初期化する、代入とは異なる// こう書いても同じ意味になる

31// これは代入

3536cout << endl;

37

b.setary(7, 99);

38

b.print();

39

c.print();

40

d.print();41}
exit(EXIT_SUCCESS);

4243// 代入ではコピーコンストラクタは使われない、別処理

}

44
45

human::human(int as)

46

{

47

cout << "Constructor was called." << endl;

48

arysize = as;49

ary = NULL;50

if (0 < arysize) {51

ary = new int[arysize];52

for (int i = 0; i < arysize; i++) ary[i] = i;

53

}

54

else {

55

arysize = 0;56ary = NULL;
}

57
58

}

59
60

human::human(const human& org)

61

{

62

cout << "Copy constructor was called." << endl;

63

arysize = org.arysize;64

ary = new int[arysize];for (int i = 0; i < arysize; i++) ary[i] = org.ary[i];

65
66

}

67
68

human::~human()

69

{

70

cout << "Destructor was called. " << endl;(次のページに続く)

6.11. コピーコンストラクタ（動的メモリ確保）※

127

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

delete[] ary;

71
72

}

73
74

void human::print()

75

{

76

cout << arysize << "77

if (0 < arysize) {address:" << ary << endl;

78

for (int i = 0; i < arysize; i++) cout << ary[i] << " ";79

cout << endl;
}

80
81

}

82
83

void human::setary(int idx, int val)

84{
ary[idx] = val;

85
86

}
独自に用意したコピーコンストラクタでは、• 配列のサイズを元からコピーして、• 先頭アドレスをコピーするのではなく、new で領域を確保して、• そこに元の配列の値をコピーしている
これでコピー元の持つ配列とは別の場所を確保して同じ値をそこに保持する。実行結果が以下である。途
中、コピーコンストラクタが呼び出されていることがわかる。また、b の配列の要素を書き換えても、c の要素は影響を受けないことがわかる。ただし、コピーコンストラクタでは代入の処理はカバーしないので、代入の処理が必要な場合は、同じ要領で独自の処理を用意する必要がある。

$ ./6-10
Constructor was called.
0

address:0

Constructor was called.
20

address:0x56270af6e280

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Copy constructor was called.
20

address:0x56270af6e2e0

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Constructor was called.
20

address:0x56270af6e280

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
20

address:0x56270af6e280

0 1 2 3 4 5 6 99 8 9 10 11 12 13 14 15 16 17 18 19
20

address:0x56270af6e2e0

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19(次のページに続く)128

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

20

address:0x56270af6e280

0 1 2 3 4 5 6 99 8 9 10 11 12 13 14 15 16 17 18 19
Destructor was called.
Destructor was called.
Destructor was called.
Destructor was called.
この結果を見ると、最後に b, c, d の内容を表示した時に、b の配列の途中の要素に 99 を代入したので b の
結果には 99 が含まれている。c は初期化時に正しい処理をするコピーコンストラクタが使用されたので、配列は別領域を確保する正しい処理により 99 が含まれない。その一方、d には b が代入されたが、その代入の処理が正しくないので、d が持つ配列は b と同じ場所を指しており、結果として b の配列に代入した

99 が d の結果にも表れてしまっており、これは望ましい結果ではない。

6.12 this, メンバ関数の呼び出し元のオブジェクトを指し示すポインタ
メンバ関数を呼び出したとき、そのメンバ関数が属するオブジェクトを指し示す特殊なポインタ this が自動的に用意される。これはメンバ関数内で、呼び出し元のオブジェクトを参照、操作する時に使用する。
次のプログラムは、N 次元のベクタを扱うクラス myvector を実装したサンプルプログラムである。メンバ
変数には次元数を表す int dim と、各次元の値を記憶する ﬂoat ＊ ary がある。前節までの内容を反映して、必要な処理を行うコンストラクタ、デストラクタ、コピーコンストラクタを定義している。
★練習コンパイルして実行してみよう。
リスト 6.12: 6-11.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-11 6-11.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7

const float V0[] = {0, 0, 0};89class myvector {10

private:

11

int

12

float

13

dim;
*ary;

public:14

myvector(const int dnum = 3, const float a[] = V0);

15

myvector(const myvector& org);

16
17~myvector();void print();(次のページに続く)

6.12. this, メンバ関数の呼び出し元のオブジェクトを指し示すポインタ

129

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
18

void setvec(const float a[]);19

void getvec(float a[]);20

void f1(int dim);

21

};

22
23

void f2(myvector *p);24
25

int main(void)

26

{

27

myvector

28

v1.print();

v1;

29
30

myvector

31

v2.print();v2(3);

32
33

float a[] = {1, 2, 3};34

myvector

35

v3.print();v3(3, a);36

v3.f1(10);3738

exit(EXIT_SUCCESS);

39
40

}

41
42

myvector::myvector(const int dnum, const float a[])

43

{

44

dim = dnum;45

ary = NULL;

46

if (0 < dim) {47

ary = new float[dim];48

for (int i = 0; i < dim; i++) ary[i] = a[i];

49

}

50

else {

51

dim = 0;52

ary = NULL;
}

53
54

}

55
56

myvector::myvector(const myvector& org)

57

{

58

dim = org.dim;59

ary = new float[dim];60

for (int i = 0; i < dim; i++) ary[i] = org.ary[i];

61

}

62

(次のページに続く)

130

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
63

myvector::~myvector()

64{
delete[] ary;

65
66

}

67
68

void myvector::print()

69

{

70

//

71
72
73
74
75
76

cout << "dim: " << dim << endl;// 普通はこう書くが、
cout << "dim: " << this -> dim << endl; // こう書くこともできる
if (0 < dim) {for (int i = 0; i < dim; i++) cout << ary[i] << " ";cout << endl;}
cout << "Adrs: " << this << endl; // 呼び出し元のオブジェクトのアドレス

77

f2(this);78
79// 別の関数に呼び出し元のオブジェクトを渡して処理できる

}

80
81

void myvector::setvec(const float a[])82{
for (int i = 0; i < dim; i++) ary[i] = a[i];

83
84

}

85
86

void myvector::getvec(float a[])87{for (int i = 0; i < dim; i++) a[i] = ary[i];

88
89

}

90
91

void myvector::f1(int dim)92{// this により引数とメンバ変数を区別できる
cout << this -> dim << endl;
cout << dim << endl;

93
94
95

}

96
97

void f2(myvector *p)

98{
cout << "Adrs: " << p << endl;

99
100

}
メンバ関数 print() と f1(), f2() に this の使用例がある。

• メンバ関数内でメンバ変数を参照する場合は、変数名だけ記述すればよいが、this ポインタ経由でも同じように参照できる (70, 71 行)。

• this によりオブジェクトの先頭アドレスを読み出すことができる (76 行)。
• 他の関数にオブジェクトを引数として渡して処理をさせることができる (78 行)。

6.12. this, メンバ関数の呼び出し元のオブジェクトを指し示すポインタ

131

Robot Programming II Documentation, リリース 1.0• 引数とメンバ変数の名前が重複した場合に、メンバ変数を区別して指定できる (93, 94 行)。
以下は実行結果である。

$ ./6-11
dim: 30 0 0
Adrs: 0x7fff542a3b90
Adrs: 0x7fff542a3b90
dim: 30 0 0
Adrs: 0x7fff542a3ba0
Adrs: 0x7fff542a3ba0
dim: 3
1 2 3
Adrs: 0x7fff542a3bb0
Adrs: 0x7fff542a3bb0
10
3
this ポインタを使う機会はそれ程多くないが、これを使わないと実現できない機能がある。次に示す演算子のオーバーロードでは this ポインタを使った実装例が出てくる。

6.13 演算子のオーバーロード
この節では、演算子のオーバーロードを取り上げる。C++ では、最初から用意されている様々な演算子 (=,+, -, ＊, / など) に対して、新しい機能を定義することができる。ここではその対象として、代入演算子 + にクラス固有の機能を追加する。３次元ベクトルを表すクラス myvector に対して、２個のベクトル間の加算処理が行えるようにする。
次に示すのがベクトル間の加算処理を既存の演算子 + に追加したプログラムの例である。
★練習コンパイルして実行してみよう。
リスト 6.13: 6-12a.cpp
1
2
3// g++ -Wall -ansi -std=c++11 -o 6-12a 6-12a.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7

const int dim = 3;8

const float V0[] = {0, 0, 0};9(次のページに続く)

132

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
10class myvector {11

private:
float

12
13

ary[dim];public:14

myvector();15

myvector(const myvector& org);

16~myvector();17

myvector operator + (const myvector& rop);18

void print();

19

void setvec(const float a[]);20

void getvec(float a[]);

21

};

22
23

int main(void)

24{
myvector

25

v1, v2;26

28

float a[] = {1, 2, 3};v1.setvec(a);29

v1.print();

27

30

v2 = v1;31

v2.print();

32
33

myvectorv3;34

v3 = v1 + v2;

35v3.print();36

exit(EXIT_SUCCESS);

37
38

}

39
40

myvector::myvector()

41{
setvec(V0);

42
43

}

44
45

myvector::myvector(const myvector& org)

46{
setvec(org.ary);

47
48
49

//
}

for (int i = 0; i < dim; i++) ary[i] = org.ary[i];5051

myvector::~myvector()

53{// nothing to do

54

}

52

(次のページに続く)

6.13. 演算子のオーバーロード

133

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
55
56

myvector myvector::operator + (const myvector& rop)

57

{

58

myvector tmp;

59

for (int i = 0; i < dim; i++) tmp.ary[i] = ary[i] + rop.ary[i];

60

//

61
62

for (int i = 0; i < dim; i++) tmp.ary[i] = this.ary[i] + rop.ary[i];return tmp;

}

63
64

void myvector::print()

65

{

66

cout << "dim: " << dim << endl;

67

if (0 < dim) {68

for (int i = 0; i < dim; i++) cout << ary[i] << " ";69

cout << endl;}

70

cout << "Adrs: " << this << endl << endl;

71
72

}

73
74

void myvector::setvec(const float a[])75{
for (int i = 0; i < dim; i++) ary[i] = a[i];

76
77

}

78
79

void myvector::getvec(float a[])80{for (int i = 0; i < dim; i++) a[i] = ary[i];

81
82

}
myvector 型のオブジェクト v1, v2 を定義して、v1 には初期値として (1 2 3) を代入している。次に v2 に v1の値を代入する。その後 v3 を生成して v1, v2 を加算した結果を代入し、結果を表示している。

56 行目から 62 行目までが演算子 + に対するオーバーロードの定義である。この場合演算子 + は左側にあるオブジェクトから呼び出され、右側にあるオブジェクトが引数として扱われる。34 行の記述 v3 = v1 +

v2; では、v1 から呼び出され、v2 が引数となる。返り値は myvector 型となり、次の演算や代入の対象として扱われる。この例では、v1, v2 の各次元の要素を取り出して加算した結果を tmp の対応する次元の値として代入する。
実行結果が次になる。v1, v2 は (1 2 3) となっており、v3 の値が (2 4 6) と表示されていることから加算が正しく実行されていることがわかる。

$ ./6-12a
dim: 3
1 2 3
Adrs: 0x7ffd1f61882c(次のページに続く)

134

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

dim: 3
1 2 3
Adrs: 0x7ffd1f618838
dim: 3
2 4 6
Adrs: 0x7ffd1f618850
加算の処理は整数や実数の単一の値の間では言語仕様として最初から定義、用意されているが、ベクトルなどの複雑な構造を持つデータについては用意されていないので、自分で独自に定義する必要がある。C
言語には演算子のオーバーロードの機能がないので、次の例のように関数として定義するしか方法がない。

myvector addvec(myvector v1, myvector v2);
struct myvector v1, v2, v3;
v3 = addvec(v1, v2);
C++ では演算子のオーバーロードが利用できるので、関数にしなくても既存の演算子 + に機能を追加することで実現することができる。既存の演算子を使った自然な記述が可能となることで、可読性が増すと言える。

6.14 演算子のオーバーロード（動的メモリ確保）※
コピーコンストラクタの節では、動的に確保する配列を持つオブジェクトにおいて、その配列を適切に扱う専用の処理を備えたコピーコンストラクタの必要性と具体的な処理の内容を取り上げた。コピーコンス
トラクタにより、初期化時に別のオブジェクトの内容をコピーすることはできるようになった。しかし代入の処理では、正しく動的に確保した配列をまだ扱えないことを示した。正しく扱えない理由は、標準的に用意される代入の処理は、標準的なコピーコンストラクタと同じように個々のメンバ変数の値を単純にコピー先に書き込むからであり、動的に確保したメモリ内のデータのコピーに対応しないからである。
この節では、代入演算子 = にクラス固有の機能を追加する処理を取り上げる。動的に確保する配列を持つ
クラス myvector に対して、適切に代入処理が行えるようにする。
次のプログラムでは、標準的な代入の処理ではどのような問題が生じるかを確認する。
★練習
コンパイルして実行してみよう。
リスト 6.14: 6-12.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-12 6-12.cpp#include<iostream>#include<cstdlib>4(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

135

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
5

using namespace std;

6
7

const float V0[] = {0, 0, 0};89class myvector {10

private:

11

int

12

float

13

dim;
*ary;

public:14

myvector(const int dnum = 3, const float a[] = V0);

15

myvector(const myvector& org);

16~myvector();17

void print();18

void setvec(const float a[]);19

void getvec(float a[]);

20

};

21
22
23

int main(void){

24

myvector

25

v2 = v1;26

v1.print();

27

v2.print();

v1, v2;28
29

float a[] = {1, 2, 3};30

v1.setvec(a);31

v1.print();

32

v2.print();33

exit(EXIT_SUCCESS);

34
35

}

36
37

myvector::myvector(const int dnum, const float a[])

38

{

39

dim = dnum;40

ary = NULL;

41

if (0 < dim) {42

ary = new float[dim];43

for (int i = 0; i < dim; i++) ary[i] = a[i];

44

}

45

else {

46

dim = 0;47

ary = NULL;
}

48
49

}
(次のページに続く)

136

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
50
51

myvector::myvector(const myvector& org)

52

{

53

dim = org.dim;54

ary = new float[dim];for (int i = 0; i < dim; i++) ary[i] = org.ary[i];

55
56

}

57
58

myvector::~myvector()

59{
delete[] ary;

60
61

}

62
63

void myvector::print()

64

{

65

cout << "dim: " << dim << endl;

66

if (0 < dim) {for (int i = 0; i < dim; i++) cout << ary[i] << " ";cout << endl;

67
68

}

69

cout << "Adrs: " << this << endl << endl;

70
71

}

72
73

void myvector::setvec(const float a[])74{
for (int i = 0; i < dim; i++) ary[i] = a[i];

75
76

}

77
78

void myvector::getvec(float a[])79{
for (int i = 0; i < dim; i++) a[i] = ary[i];

80
81

}
myvector 型のオブジェクト v1, v2 を定義して、25 行目で代入している。その後、v1 の要素の値を書き換えた後に v1, v2 の内容を表示している。実行結果が次になる。

$ ./6-12
dim: 30 0 0
Adrs: 0x7ffc0ebab4d0
dim: 30 0 0
Adrs: 0x7ffc0ebab4e0(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

137

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

dim: 3
1 2 3
Adrs: 0x7ffc0ebab4d0
dim: 3
1 2 3
Adrs: 0x7ffc0ebab4e0
2 回目に内容を表示した時に、v1 の要素を変更したのに、v2 の値も同じ値になってしまっている。これは代入処理の時に、配列の内容ではなく、先頭アドレスだけがコピーされたために v2 の方でも同じ領域を指し示しているからである。コピーコンストラクタの時と同じ問題が生じている。
次に示すのは、代入演算子 = にクラス myvector 用の専用の処理、具体的には動的に確保した配列の内容を適切に複製する処理を指定したプログラムである。
★練習コンパイルして実行してみよう。
リスト 6.15: 6-13.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-13 6-13.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7

const float V0[] = {0, 0, 0};

8
9
10
11

class myvector {private:
int
float

12
13

dim;
*ary;

public:14

myvector(const int dnum = 3, const float a[] = V0);

15

myvector(const myvector& org);

16~myvector();17

void operator = (const myvector& org);

18

void print();

19

void setvec(const float a[]);void getvec(float a[]);

20
21

};

22
23

int main(void)

24

{

25

myvector

26

v2 = v1;

v1, v2;(次のページに続く)138

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
27

v1.print();

28

v2.print();

29
30

float a[] = {1, 2, 3};31

v1.setvec(a);32

v1.print();

33

v2.print();

34/*
myvectorv3;
v3 = v2 = v1; // 連続した代入はまだ対応できない
v1.print();
v2.print();
v3.print();

35
36
37
38
39
40

*/
exit(EXIT_SUCCESS);

41
42

}

43
44
45

myvector::myvector(const int dnum, const float a[]){46

dim = dnum;47

ary = NULL;

48

if (0 < dim) {49

ary = new float[dim];50

for (int i = 0; i < dim; i++) ary[i] = a[i];

51

}

52

else {
dim = 0;53

ary = NULL;

54

}

55
56

}

57
58

myvector::myvector(const myvector& org)

59

{

60

dim = org.dim;61

ary = new float[dim];for (int i = 0; i < dim; i++) ary[i] = org.ary[i];

62
63

}

64
65

myvector::~myvector()

66{
delete[] ary;

67
68

}

69
70

void myvector::operator = (const myvector& org)71{
(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

139

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
72

dim = org.dim;73

float* tmp = new float[dim];74

delete[] ary;75

ary = tmp;

76

for (int i = 0; i < dim; i++) ary[i] = org.ary[i];

77

}

78
79

void myvector::print()

80

{

81

cout << "dim: " << dim << endl;

82

if (0 < dim) {for (int i = 0; i < dim; i++) cout << ary[i] << " ";83

cout << endl;

84
85

}

86

cout << "Adrs: " << this << endl << endl;

87

}

88
89
90

void myvector::setvec(const float a[]){for (int i = 0; i < dim; i++) ary[i] = a[i];

91
92

}

93
94

void myvector::getvec(float a[])95{for (int i = 0; i < dim; i++) a[i] = ary[i];

96
97

}
演算子の再定義、オーバーロードは、17 行目にプロトタイプ宣言がある。書式は通常のメンバ関数と同じ形式になるが、関数名の代わりに、operator 演算子 () となる。また定義の実体は 70 行目からある。代入演
算子 = の左辺が呼び出し元のオブジェクト、右辺が引数とされる。例として、

myvector a, b;
a = b;
であれば、a が呼び出し元 (this で参照可能)、b が引数 org となる。定義の内容は、72 行目で次元数のコ
ピー、73 行目で新しい配列の確保、74 行目で古い配列の解放、75 行目で配列の先頭アドレスのコピー、76
行目で配列の内容のコピーをしている。新しく配列を確保し直すのは、次元数が異なっても正しく処理できるようにするためである。よって、この実装では次元数が異なるオブジェクト間でも代入が可能である。
実行結果を次に示す。

$ ./6-13
dim: 30 0 0
Adrs: 0x7ffd9171b700(次のページに続く)140

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)

dim: 30 0 0
Adrs: 0x7ffd9171b710
dim: 3
1 2 3
Adrs: 0x7ffd9171b700
dim: 30 0 0
Adrs: 0x7ffd9171b710
6-12.cpp では、v1 の配列を変更した時に v2 も値が変わったように見えたが、この結果では v1 の配列を変更しても v2 には影響しない、すなわち正しく配列がコピーされていることがわかる。
ここまでで単一の代入は正しく処理できるようになった。しかし、37 行目でコメントアウトされているような連続した代入はまだできない。この部分を実行しようとすると、コンパイル時にエラーとなってしまう。
連続した代入が可能となるように代入処理を拡張すると、次に示す 6-15.cpp となる。6-13.cpp との違いは、void operator = () から myvector& operator = () のように、返り値として代入されたオブジェクトの参照を返すようにした点である。この変更により、右から順に代入処理を進めたときに、2 回目の代入時に代入すべきオブジェクトが存在することになる。76 行目の return が変更点である。
★練習コンパイルして実行してみよう。
リスト 6.16: 6-15.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-15 6-15.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7

const float V0[] = {0, 0, 0};89class myvector {10

private:

11

int

12

float

13

dim;
*ary;

public:14

myvector(const int dnum = 3, const float a[] = V0);

15

myvector(const myvector& org);

16~myvector();17

myvector& operator = (const myvector& org);(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

141

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
18

void print();

19

void setvec(const float a[]);20

void getvec(float a[]);

21

};

22
23

int main(void)

24

{

25

float a[] = {1, 1, 1};26

myvector

v1(3, a), v2, v3;27
28

v1.print();

29

v2.print();

30

v3.print();31
32

a[1] = 2; a[2] = 3;33

v1.setvec(a);34v3 = v2 = v1;35
36v1.print();

37

v2.print();

38

v3.print();39

exit(EXIT_SUCCESS);

40
41

}

42
43

myvector::myvector(const int dnum, const float a[])

44

{

45

dim = dnum;46

ary = NULL;

47

if (0 < dim) {ary = new float[dim];48

for (int i = 0; i < dim; i++) ary[i] = a[i];

49
50

}

51

else {
dim = 0;52

ary = NULL;

53

}

54
55

}

56
57

myvector::myvector(const myvector& org)58{
dim = org.dim;59

ary = new float[dim];for (int i = 0; i < dim; i++) ary[i] = org.ary[i];

60
61
62

}
(次のページに続く)142

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
63
64

myvector::~myvector()65{
delete[] ary;

66
67

}

68
69

myvector& myvector::operator = (const myvector& org)

70

{

71

dim = org.dim;

72

float* tmp = new float[dim];73

delete[] ary;74

ary = tmp;75

for (int i = 0; i < dim; i++) ary[i] = org.ary[i];76return *this;

77

}

78
79

void myvector::print()

80

{

81

cout << "dim: " << dim << endl;

82

if (0 < dim) {83

for (int i = 0; i < dim; i++) cout << ary[i] << " ";84

cout << endl;

85

}

86

cout << "Adrs: " << this << endl << endl;

87

}

88
89

void myvector::setvec(const float a[])90{for (int i = 0; i < dim; i++) ary[i] = a[i];

91
92

}

93
94

void myvector::getvec(float a[])95{for (int i = 0; i < dim; i++) a[i] = ary[i];

96
97

}
実行結果を次に示す。連続した代入の結果が正しく得られていることがわかる。

$ ./6-15
dim: 3
1 1 1
Adrs: 0x7ffc94e8a6e0
dim: 30 0 0(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

143

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

Adrs: 0x7ffc94e8a6f0
dim: 30 0 0
Adrs: 0x7ffc94e8a700
dim: 3
1 2 3
Adrs: 0x7ffc94e8a6e0
dim: 3
1 2 3
Adrs: 0x7ffc94e8a6f0
dim: 3
1 2 3
Adrs: 0x7ffc94e8a700
次に示すのは、もう一つの例としてクラス myvector の加算を演算子 + で実行できるようにする定義である。プロトタイプ宣言が 18, 19 行、実体の定義が 92 行から 121 行目となる。
★練習コンパイルして実行してみよう。
リスト 6.17: 6-16.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 6-16 6-16.cpp#include<iostream>#include<cstdlib>

4
5

using namespace std;

6
7

const float V0[] = {0, 0, 0};89class myvector {10

private:

11

int

12

float

13

dim;
*ary;

public:14

myvector(const int dnum = 3, const float a[] = V0);

15

myvector(const myvector& org);

16~myvector();17

myvector& operator = (const myvector& org);

18

myvector operator + (const myvector& rop);19

myvector operator + (const float f);(次のページに続く)

144

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
20

void print();21

void setvec(const float a[]);22

void getvec(float a[]);

23

};

24
25

int main(void)

26

{
{

27
28

float a[] = {1, 1, 1};29

myvector

v1(3, a), v2, v3;30
31

v1.print();

32

v2.print();

33

v3.print();34

a[1] = 2; a[2] = 3;35

v1.setvec(a);v3 = v2 = v1;

36
37
38
39

v1.print();

40

v2.print();

41

v3.print();

42

v3 = v2 + v1;

43
44v3.operator=(v2.operator+(v1)); 前行の式はこのように書くのと等価
v3.print();

//

45
46
47

v3 = v3 + 10; // 今回の実装では v3 = 10 + v3; とは書けないので注意する

48

v3.print();}49

exit(EXIT_SUCCESS);

50
51

}

52
53

myvector::myvector(const int dnum, const float a[])

54

{

55

cout << "Constructor called, " << this << endl;

56

dim = dnum;57

ary = NULL;

58

if (0 < dim) {59

ary = new float[dim];60

for (int i = 0; i < dim; i++) ary[i] = a[i];

61
62
63
64

}
else {
dim = 0;ary = NULL;(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

145

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

}

65
66

}

67
68

myvector::myvector(const myvector& org)

69

{

70

cout << "Copy Constructor called, " << this << endl;

71

dim = org.dim;72

ary = new float[dim];73

for (int i = 0; i < dim; i++) ary[i] = org.ary[i];

74

}

75
76

myvector::~myvector()

77

{

78

delete[] ary;

79

cout << "Destructor called, " << this << endl;

80

}

81
82

myvector& myvector::operator = (const myvector& org)

83

{

84

dim = org.dim;85

float* tmp = new float[dim];86

delete[] ary;87

ary = tmp;

88

for (int i = 0; i < dim; i++) ary[i] = org.ary[i];return *this;

89
90

}

91
92

myvector myvector::operator + (const myvector& rop)

93{
const int MAXDIM = 10;

94
95

if (dim != rop.dim) {96
97

cerr << "Error: dimension not match." << endl;

98

exit(EXIT_FAILURE);

99

}

100

if (MAXDIM < dim) {101

cerr << "Error: Too large dimension." << endl;

102

exit(EXIT_FAILURE);
}

103
104
105

float a[MAXDIM];

106

for (int i = 0; i < dim; i++) a[i] = ary[i] + rop.ary[i];107return myvector(dim, a);

108

}

109

(次のページに続く)146

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0(前のページからの続き)
110

myvector myvector::operator + (const float f)

111{
const int MAXDIM = 10;

112
113

if (MAXDIM < dim) {

114
115

cerr << "Error: Too large dimension." << endl;

116

exit(EXIT_FAILURE);

117

}

118

float a[MAXDIM];

119

for (int i = 0; i < dim; i++) a[i] = ary[i] + f;120

return myvector(dim, a);

121

}

122
123

void myvector::print()

124

{

125

cout << "dim: " << dim << endl;

126

if (0 < dim) {for (int i = 0; i < dim; i++) cout << ary[i] << " ";cout << endl;

127
128

}

129

cout << "Adrs: " << this << endl << endl;

130
131

}

132
133

void myvector::setvec(const float a[])134{
for (int i = 0; i < dim; i++) ary[i] = a[i];

135
136

}

137
138

void myvector::getvec(float a[])139{
for (int i = 0; i < dim; i++) a[i] = ary[i];

140
141

}
実行結果を以下に示す。

$ ./6-16
Constructor called, 0x7ffc9ae68490
Constructor called, 0x7ffc9ae684a0
Constructor called, 0x7ffc9ae684b0
dim: 3
1 1 1
Adrs: 0x7ffc9ae68490
dim: 30 0 0(次のページに続く)

6.14. 演算子のオーバーロード（動的メモリ確保）※

147

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

Adrs: 0x7ffc9ae684a0
dim: 30 0 0
Adrs: 0x7ffc9ae684b0
dim: 3
1 2 3
Adrs: 0x7ffc9ae68490
dim: 3
1 2 3
Adrs: 0x7ffc9ae684a0
dim: 3
1 2 3
Adrs: 0x7ffc9ae684b0
Constructor called, 0x7ffc9ae684c0
Destructor called, 0x7ffc9ae684c0
dim: 3
2 4 6
Adrs: 0x7ffc9ae684b0
Constructor called, 0x7ffc9ae684c0
Destructor called, 0x7ffc9ae684c0
dim: 312 14 16
Adrs: 0x7ffc9ae684b0
Destructor called, 0x7ffc9ae684b0
Destructor called, 0x7ffc9ae684a0
Destructor called, 0x7ffc9ae68490
この結果から、vector 同士の加算、及び各要素への定数の加算ができていることがわかる。

6.15 まとめこの章の目標• オブジェクト指向を理解する。
• class の記述の仕方を理解する。自分で記述できる。
• private, public を適切に指定して、カプセル化を実現できる。

148

第 6 章 オブジェクト指向 1、classRobot Programming II Documentation, リリース 1.0• コンストラクタ、デストラクタの機能を理解する。自分で記述できる。
• メンバ関数によるアクセス制限の必要性を理解する。自分で記述できる。
• コピーコンストラクタの機能を理解する。
• メンバ変数に配列やポインタがある場合に特別な処理が必要になる理由を理解する。必要な機能を自分で実装できる。

• 演算子のオーバーロードを理解する。必要な機能を自分で実装できる。
練習問題

1. 6-2.cpp で、38 行目の /＊、47 行目の ＊/ を削除して、実行してみなさい。
2. 前問に続けて class human の定義中の private を public に変更して、実行してみなさい。
3. メンバ変数 name を操作する、setname(), getname() を作成して動作を確認しなさい。間違いを防ぐ
チェック機能を組み込みなさい。(e6-34567.cpp)

4. メンバ変数 nation を操作する、setnation(), getnation() を作成して動作を確認しなさい。間違いを防ぐ
チェック機能を組み込みなさい。(e6-34567.cpp)

5. メンバ変数 age を操作する、setage(), getage() を作成して動作を確認しなさい。間違いを防ぐチェック機能を組み込みなさい。(e6-34567.cpp)

6. メンバ変数 height を操作する、setheight(), getheight() を作成して動作を確認しなさい。間違いを防ぐ
チェック機能を組み込みなさい。(e6-34567.cpp)

7. メンバ変数 weight を操作する、setweight(), getweight() を作成して動作を確認しなさい。間違いを防ぐチェック機能を組み込みなさい。(e6-34567.cpp)

8. コンストラクタとコピーコンストラクタではどのような違いがあるか説明しなさい。
9. デストラクタで行うべき処理を説明しなさい。
10. メンバ変数に動的メモリを確保する配列 (ポインタ) があると、通常のコピーコンストラクタではどのような問題が生じるか説明しなさい。

11. n 次元ベクタを表す class myvector をコンストラクタとデストラクタを含めて実装しなさい。メンバ
変数は次元を表す int dim; と各次元の要素を保持する配列のアドレスを保持する ﬂoat ＊ ary; とする。
配列は dim の値に基づき実行時に動的に確保しなさい。(e6-11121314.cpp)

12. n 次元ベクタを定数倍するメンバ関数 void mulvec(ﬂoat v); を作成しなさい。(e6-11121314.cpp)
13. class myvector に対して乗算の演算子 ＊ の機能を拡張、オーバーロードして、n 次元ベクタを定数倍できるようにしなさい。(e6-11121314.cpp)

14. class myvector に対して減算の演算子 - の機能を拡張、オーバーロードして、n 次元ベクタ間で減算ができるようにしなさい。(e6-11121314.cpp)
15. 6-12a.cpp と同じ形式で 3 次元ベクタを表す class myvector を操作するプログラムにおいて、3 次元ベクタを定数倍するメンバ関数 void mulvec(ﬂoat v); を作成しなさい。(e6-1516171819.cpp)

16. 3 次元ベクタを表す class myvector において、乗算の演算子 ＊ の機能を拡張、オーバーロードして、
3 次元ベクタを定数倍できるようにしなさい。(e6-1516171819.cpp)

6.15. まとめ149

Robot Programming II Documentation, リリース 1.0
17. 3 次元ベクタを表す class myvector において、減算の演算子 - の機能を拡張、オーバーロードして、 3次元ベクタ間で減算ができるようにしなさい。(e6-1516171819.cpp)

18. 3 次元ベクタを表す class myvector において、演算子 / の機能を拡張、オーバーロードして、 3 次元
ベクタ間の内積を計算して返り値とする機能を実装しなさい。(e6-1516171819.cpp)

19. 3 次元ベクタを表す class myvector において、演算子 % の機能を拡張、オーバーロードして、 3 次元
ベクタ間の外積を計算しなさい。更に結果のベクタを正規化して返り値とする機能を実装しなさい。

(e6-1516171819.cpp)

150

第 6 章 オブジェクト指向 1、class151

第 7 章 オブジェクト指向 2、継承

7.1 継承class には階層的な概念をデータとして適切に表現できるようにする継承 (inheritance, inherit) と呼ばれる機能が言語仕様として用意されている。上位概念を表現する class1 を定義しておいて、class1 に基づく下位概念 class 2 を定義する時に、class1 を継承すると指定しておくと、class1 に含まれるメンバ変数やメン
バ関数を自動的に class2 に持たせることができる。
上位概念のクラスを 基底クラス 、下位概念のクラスを 派生クラスと呼ぶ。また 親クラス 、 子クラス と呼ぶこともある。継承により、クラス間の上位/下位、親子関係を表現できる。
これは現実世界における知識の表現と同じ原理、考え方となっている。例として、「動物はものを食べる」、「動物は子孫を作る」、などの知識を上位概念として持っていて、「哺乳類は動物である」、「犬は哺乳類である」、「ヒトは哺乳類である」などの知識が与えられれば、「犬はものを食べるか？」「ヒトはものを食べるか」などの質問に容易に答えることができる。このとき、「〇〇が動物であれば、〇〇は動物の性質を備えている」という推論ができるからである。継承はこの推論をプログラムとして直接表現できる。継承により、「犬はものを食べる」、「ヒトはものを食べる」などの知識を個別に記述する必要がなくなる。
以下に示す 7-1.h, 7-1.cpp は、前章のヒトを表す class のサンプルプログラム 6-2.cpp をもとにして、継承の機能を利用して動物が持つ性質を次の図のように階層的に表現する。
★練習
コンパイルして実行してみよう。
動物↓
哺乳類、鳥、爬虫類、魚、昆虫↓
ヒト

リスト 7.1: 7-1.h1
2
3// g++ -Wall -ansi -std=c++11 -o 7-1 7-1.cpp#include<iostream>#include<cstdlib>

4
5

const int

NO

= 0;

6

const int

YES

= 1;

const int

MALE

= 0;

7
8

(次のページに続く)

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

const int

FEMALE= 1;

11

const int

JAPAN

= 0;

12

const int

US

= 1;

9
10

13
14

using namespace std;

15
16

class animal {17

protected:

18

int live;

19

int eat;20

string move;21

string temp;

22

string breed

23// 生きている// ものを食べる// 移動する// 体温; // 繁殖public:24

animal(){cout << "Constructor animal" << endl; live = YES; eat = YES;}

25~animal(){cout << "Destructor animal" << endl;}
void fa();void print();

26
27
28

};

29
30

class insect : public animal { // 昆虫

31

public:insect(){move = "6legs,wing"; temp = "climacteric"; breed = "egg";}

32
33
34

//
};

~insect();

// ⬆

変温動物

35
36

class fish : public animal { // 魚

37

public:fish(){move = "fin"; temp = "climacteric"; breed = "egg";}

38
39
40

//
};

~fish();

41
42

class reptiles : public animal { // 爬虫類

43

public:

44
45
46

//
};

reptiles(){move = "4legs"; temp = "climacteric"; breed = "egg";}~reptiles();

47
48

class bird : public animal { // 鳥

49

public:bird(){move = "2legs,wing"; temp = "constant"; breed = "egg";}

50
51
52

//
};

~bird();

// ⬆

恒温動物

53

(次のページに続く)

152

第 7 章 オブジェクト指向 2、継承

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
54

class mammalian : public animal { // 哺乳類、animal の派生クラス

55

public:mammalian(){cout << "Constructor mammalian" << endl;

56

move = "4legs"; temp = "constant"; breed = "child,milk";}

57~mammalian(){cout << "Destructor mammalian" << endl;}

58

void fm();void print(); // 現 class 用の関数を作成しないと基底クラスのものが使われる

59
60
61

//
};

62
63

class human : public mammalian { // ヒト、mammalian の派生クラス

64

private:65

string

66

int

67

int

sex;

68

int

age;

69

float

height; // meterfloat

weight; // kilo gram

70
71
72

name;
nation; // nationality 国籍// MALE or FEMALE

public:human(string str = "noname", int n = 0, int s = 0, int a = 0,
float h = 0, float w = 0);7374~human(){cout << "Destructor human" << endl;}

75

void set(string name, int n, int s, int a, float h, float w);

76

void fh();77

void print(); // 現 class 用の関数を作成するとそれが使われる

78};

リスト 7.2: 7-1.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 7-1 7-1.cpp#include<iostream>#include<cstdlib>#include"7-1.h"5
6

using namespace std;

7
8

int main(void)

9

{

10

{

11

animal12

mammalian

13

human

a;
m;
h("Chubu Taro", JAPAN, MALE, 20, 1.75, 62.0);

14
15

a.print();

16

m.print(); // この場合は animal::print() が呼ばれる

17

h.print(); // human::print() が呼ばれる(次のページに続く)

7.1. 継承

153

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
18
19h.fa(); // human からでも animal::fa() を呼び出し可能

20

h.fm();

21h.fh();}

22

exit(EXIT_SUCCESS);

23
24

}

25
26

void animal::fa()27{
cout << "member function animal" << endl;

28
29

}

30
31

void animal::print()

32{
cout << "live:" << live

33

<< "

34
35

eat:" << eat << endl;

}

36
37

void mammalian::fm()

38{
cout << "member function mammalian" << endl;

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

}

/*
void mammalian::print() // mammalian 用の print() を定義するとこれが使われる{
cout << "live:" << live<< " eat:" << eat<< " move:" << move<< " temp:" << temp<< " breed:" << breed << endl;}*/
human::human(string str, int n, int s, int a, float h, float w){
cout << "Constructor human" << endl;
name = str;
nation = n;sex = s;age = a;height = h;weight = w;
}

61
62

void human::set(string str, int n, int s, int a, float h, float w)(次のページに続く)154

第 7 章 オブジェクト指向 2、継承

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
63
64
65

{

// 略
}

66
67

void human::fh()68{
cout << "member function human" << endl;

69
70

}

71
72

void human::print()

73{
cout << "name:" << name

74
75

<< "

nation:" << nation

76

<< "

sex:" << sex

77

<< "

age:" << age

78

<< "

height:" << height<< "weight:" << weight << endl;

79

cout << "live:" << live<< " eat:" << eat

80
81
82

<< "

move:" << move

83

<< "

temp:" << temp

84

<< "

breed:" << breed << endl;

85}
7-1.h では
• 最上位の基底クラスの animal では、動物に共通するいくつかの性質をメンバ変数として定義している。コンストラクタでは、全体に共通する「生きている」、「ものを食べる」だけ設定する。他の変数は種類によって異なるので、ここでは設定していない。

• 2 番めの階層の動物の種類を表すクラスでは、種類固有の変数や関数は定義していないが、必要であれば定義できる。コンストラクタでは、メンバ変数にその種類固有の値を設定している。

• 最下位のクラス human は、mammalian の派生クラスとしている。これで、animal と mammalian のメ
ンバ変数、関数を継承する。human 固有のメンバ変数、関数を追加している。

7-1.cpp では• animal, mammalian, human のインスタンスを 1 個ずつ生成する。
• それぞれの print() を実行する。m.print() は実際は a.print() が実行される。
• h.fa(), h.fm(), h.fh() を実行する。それぞれは animal, mammalian, human のメンバ関数であるが、派生
クラスからは基底クラスの関数をそのまま呼び出すことができる。
以下は実行結果である。コンストラクタとデストラクタがどのような順番で呼び出されるかがわかる。例として、human のインスタンス生成時は、animal, mammalian, human の順番で呼び出される。print() がどのように実行されるかもわかる。派生クラスで関数を定義しない場合は基底クラスの関数が呼び出される。

7.1. 継承155

Robot Programming II Documentation, リリース 1.0$ ./7-1
Constructor animal
Constructor animal
Constructor mammalian
Constructor animal
Constructor mammalian
Constructor human
live:1

eat:1

live:1

eat:1

name:Chubu Tarolive:1

eat:1

nation:0
move:4legs

sex:0

age:20

temp:constantheight:1.75

weight:62

breed:child,milk

member function animal
member function mammalian
member function human
Destructor human
Destructor mammalian
Destructor animal
Destructor mammalian
Destructor animal
Destructor animal
以下は継承に関する新しい機能や記述、実行時の性質である。

7.2 アクセス指定子 protected
7-1.h 18 行目では、メンバ変数、メンバ関数のアクセスの範囲を指定するアクセス指定子として第 3 の指定子 protected が新たに使われている。

public はオブジェクト外からのアクセスを許可していて、private はオブジェクト外からのアクセスは許可しない。protected はオブジェクト外からは派生クラスだけアクセスを許可する。これにより、基底クラスと関連があり同じメンバ変数や関数を持つ派生クラスはそれらのメンバ関数、メンバ変数を継承できる。

7.3 継承するクラスの記述継承するクラスは、7-1.h 31 行目のように記述する。一般形は次のようになる。

class 派生クラス名 : public 基底クラス名 {...
}基底クラス名の前にある public は継承したメンバ変数、関数のアクセス指定を表す。ここを public にすると、基底クラスで指定された変数や関数のアクセス指定を派生クラスでもそのまま継承する。すなわち、

public のものは public に、private のものは private になる。一方、基底クラス名の前の指定を private にすると、もとのアクセス指定に関わらずすべて private になり、それ以降の派生クラスではアクセスできなく156

第 7 章 オブジェクト指向 2、継承

Robot Programming II Documentation, リリース 1.0なる。この機能により、どの段階の派生クラスにまでアクセスを許可するか指定、制限することができる。
通常は public とされることが多い。

7.4 関数のオーバーライド
関数のオーバーライドとは、基底クラスに存在するメンバ関数と同じ名前の関数を派生クラスで再定義することである。7-1.cpp では、animal::print() に対する human::print() が相当する。関数のオーバーライドを行うと派生クラスでは再定義した方の関数が実行される。関数のオーバーライドを行わない場合は、基底クラスの関数が実行される。7-1.cpp では、mammalian::print() がコメントアウトされているが、この状態で mammalian のオブジェクトから print() を実行すると、animal::print() が実行される。
関数のオーバーライドを行うと、個々のクラスに適した処理を必要に応じて用意することができる。

5.6 節で出てきた、関数のオーバーロードとよく似た名称であるが、内容が異なるので混同しないように注意する。

• 関数のオーバーロードは、同じ名前で複数の処理を用意しておいて、その中から適切なものを選択して実行する。

• 関数のオーバーライドは、基底クラスに存在する関数を派生クラスの関数で上書きする、置き換える
ような処理になる。

7.5 コンストラクタ、デストラクタが実行される順番
継承により親子関係があるクラスのオブジェクトが生成される場合、コンストラクタ、デストラクタが実行される順番は次のようになる。

• コンストラクタは、基底クラス → 派生クラスの順に呼ばれる。
• デストラクタは逆に、派生クラス → 基底クラスの順に呼ばれる。
例として、7-1.cpp で human h; としてオブジェクトを生成すると、h に対して、まず animal() が実行され、続いて mammalian() が実行され、最後に human() が実行される。基底クラスは派生クラスに何があるかを知らないのに対して、派生クラスは自分の元になっている基底クラスの構成をしっているので、場合によって派生クラスからは基底クラスのメンバ変数や関数を利用する可能性があるからである。
オブジェクトを破棄する時は逆の順番になる。基底クラス（の変数や関数）を先に破棄してしまうと、派生クラスのデストラクタが基底クラスの変数や関数をアクセスした時にエラーとなる。下位の派生クラスの破棄の処理が終わった段階で一段上の基底クラスを破棄することで問題が生じなくなる。

7.4. 関数のオーバーライド

157

Robot Programming II Documentation, リリース 1.0

7.6 まとめ
この章の目標• 継承の機能により、親子のクラス間で何が引き継がれるか理解できる。
• アクセス指定子 public, private, protected の違いがわかる。
• 継承により親子関係があるクラスについて、コンストラクタ、デストラクタが実行される順番がわかる。また、その理由を理解できる。

• 関数のオーバーライドが表す内容を理解できる。オーバーロードとの違いが理解できる。
• 継承の考え方を理解して、プログラム作成時に適切に活用できる。
練習問題

1. 動物を表す 7-1.h, 7-1.cpp に別の種類の動物 (犬、猫など) を表す派生クラスを追加する。その動物に特有のメンバ変数、メンバ関数を定義して、設定や表示、実行できることを確認する。(e7-1234.h,e7-1234.cpp)
2. 両生類を表す派生クラスを追加する。その類に特有のメンバ変数、メンバ関数を定義して、設定や表示、実行できることを確認する。(e7-1234.h, e7-1234.cpp)

3. 派生クラスとして蛙など、両生類に分類される種類を追加する。(e7-1234.h, e7-1234.cpp)
4. class animal に鳴き声を表すメンバ変数を作成して、派生クラスの種で適切な値を設定する。(e7-1234.h,e7-1234.cpp)
5. 動物以外に親子関係を持つデータを考えて、継承の機能を活用してクラスを定義してプログラムを作成する。(e7-56.cpp)

6. 前の問題で作成したプログラムにおいて、関数のオーバーライドの機能を活用して親クラス、子クラスで異なる動作をするメンバ関数を作成する。(e7-56.cpp)

158

第 7 章 オブジェクト指向 2、継承

159

第 8 章 標準テンプレートライブラリ、STL :Standard Template Library

8.1 標準テンプレートライブラリ とは何か
最初に C 言語で提供されるライブラリ関数の機能を見てみよう。C 言語では文字列処理の一連の関数群が利用できる。次のプログラムはそのような文字列を処理する関数のいくつかを利用している。
★練習コンパイルして実行してみよう。
リスト 8.1: 8-1.c
1234// gcc -Wall -ansi -std=c99 -o 8-1 8-1.c#include <stdio.h>#include <stdlib.h>#include <string.h>5
6#define

SIZE

64

7
8

int main(void)

9

{

10

int

11

char str[SIZE] = "abcde";

12char str2[SIZE];

i;

13
14

printf("%s %d\n", str, (int)strlen(str)); // 文字列の長さを求める

15

strcpy(str2, str);

16

printf("%s\n", str2);

17

printf("%s %s %d\n", str, str2,
strcmp(str, str2));

18
19// 文字列をコピーする// 文字列を比較する

for (i = 0; i < strlen(str); i++) {str[i] -= 0x20;

20
21

}

22

printf("%s %s %d\n", str, str2, strcmp(str, str2));

23

strcat(str, str2);

24

printf("%s\n", str);// 文字列を連結する

25
26

exit(EXIT_SUCCESS);(次のページに続く)

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
27

}

28

$ ./8-1
abcde 5abcde
abcde abcde 0
ABCDE abcde -32
ABCDEabcde
これらの関数を利用すると、自分で関数を書かなくても文字列の長さを求めたり、文字列を操作、加工することができる。しかし、対象が文字列 (char の配列) に限定されており、使える関数の種類も限られている。
これに対して C++ で提供される標準テンプレートライブラリ STL : Standard Template Library とは、近年の C++ の仕様の一部として標準的に提供されるライブラリ機能のうち、特にテンプレート機能を活用して 処理対象のデータの型に依存しない 形で定義、作成された様々なデータ構造とそれに付随するデータのアクセス方法、およびデータ処理の機能 (関数) の総称である。データ構造とデータ処理の機能は、それぞれ次の名称で表される。

• コンテナ Container : STL により提供されるデータ構造とそれをアクセスする機能を表す。データの入れておく場所と考えるとよい。

• イテレータ Iterator : コンテナのデータを順番に取り出す機能を表す。英語の iterate は繰り返すという意味を持ち、そこから繰り返し取り出す操作を指すと考えられる。

• アルゴリズム Algorithm : コンテナのデータに対して適用可能な処理、関数を表す。一般的にプログ
ラミングの世界でアルゴリズムという語は、計算や処理の手順を表すが、STL においては具体的な処理や関数を表すので注意する。この章ではすべて後者の意味でアルゴリズムという語を使用する。
これらは、互いに連携させて使用することを想定して作成されている。また、データ構造と処理の組み合
わせの 直交性 がなるべく保たれるように、コンテナ、イテレータ、アルゴリズムはそれぞれ設計、実装されている。
また次の C++ の機能は STL により提供されるものではないが、アルゴリズムの一部の関数で使用されるため、ここであわせて紹介しておく。

• 関数オブジェクト Function Object : オブジェクト自身に関数の機能を持たせたものを表す。
4 項目それぞれの詳細はこの後の各節で取り上げる。
STL は名前空間 std で定義されている。実際に STL を活用してプログラムを作成する時は、using で std を指定することで、名前空間の指定 std:: を省略することができる。この後示すサンプルプログラムではどの部分が STL に関する記述かわかるように、using を指定せず、STL の機能はすべて std:: を付加した状態で
記述する。

160

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0

8.2 コンテナ
コンテナとは、STL により提供されるデータ構造、およびそれにより保持されるデータを指す。コンテナには、通常の配列と同様の、要素となるデータがメモリ上で連続的に並んだ状態で記憶されるものから、
キュー、２分木など様々な種類がある。データ構造の種類に応じてデータをアクセスする方法も異なるものが用意される。
コンテナで用意、提供される代表的なデータ構造を以下に示す。

• array : 固定長配列• vector : 可変長配列、要素数の変更が可能• deque : Double Ended QUEue を省略した名称で、両端での操作が可能なキューを表す• list : 双方向連結リスト、要素間で前後両方向に連結されている• set : 順序付けられた集合• map : 連想配列、順序付けられているコンテナの機能の一部を利用して提供される コンテナアダプタ と呼ばれるデータ構造とそのためのアクセ
ス方法もある。コンテナアダプタは STL で必要とされる仕様を満たしていない部分があるため、STL で提供される機能で使えないものがある。
次のプログラムはコンテナと次節で示すイテレータの使い方を示している。コンテナの例として、比較的使う機会が多いと予想される、可変長配列を実現する vector を取り上げる。STL の vector は 6 章の classのサンプルプログラムで取り上げた myvector とは別のものであるので混同しないように注意する。
★練習
コンパイルして実行してみよう。
リスト 8.2: 8-2.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 8-2 8-2.cpp#include<iostream>#include<cstdlib>#include<vector> // 可変長の配列を実現するコンテナ vector

5
6

const int SIZE = 10;78void print(const std::vector<int> &v);

910

int main(void)

11

{

12

std::vector<int> v; // int を要素とする vector 型のコンテナの宣言

13

std::vector<int> v2(SIZE);

14

std::vector<int> v3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};1516// 初期状態

17(次のページに続く)

8.2. コンテナ161

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

std::cout << v.size() << std::endl; // size(), コンテナの要素数を求める関数

18
19

//

std::cout << v.at(0) << std::endl;
std::cout << std::endl;

20// 要素がないとこれはエラー

21
22// 値の代入23

for (int i = 0; i < SIZE; i++) {24

v.push_back(i); // 配列の末尾に値 i を新要素として追加する

25
26

}

27

for (int i = 0; i < SIZE; i++) {v2.at(i) = i*10; // 配列の添字 i 番目の場所に値を書き込む

28
29

}

30

std::cout << v.size() << std::endl; // この時点の要素数

31

std::cout << std::endl;

32
33// イテレータ、要素を一つずつ取り出してきて処理する

34

36

for (std::vector<int>::iterator it = v.begin(); it != v.end(); it++) {std::cout << *it << " "; // 先頭から順に取り出す、ポインタと同じ形式

37

}

38

std::cout << std::endl;

35

39

for (std::vector<int>::reverse_iterator rit = v.rbegin(); rit != v.rend(); rit++)

40

{

,→

std::cout << *rit << " "; // 末尾から、逆順で取り出すこともできる

41
42

}

43

std::cout << std::endl;

44

std::cout << std::endl;

45
46// auto を指定するとイテレータの適切な型を推論させることが可能

47

for (auto ai = v2.begin(); ai != v2.end(); ai++) {48

std::cout << *ai << " ";

49
50

}

51

std::cout << std::endl;

52
53

print(v3); // 同じ表示の機能を関数化した

54

std::cout << std::endl;

55
56// 可変長の機能の確認

57

v3.push_back(11); // 要素の追加

58

60

v3.push_back(12);v3.push_back(13);61

std::cout << v3.size() << " : "; print(v3);59(次のページに続く)

162

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0(前のページからの続き)
62
63

v3.erase(v3.begin() + 3, v3.begin() + 7); // 要素を４個取り除く

64

std::cout << v3.size() << " : "; print(v3);65

exit(EXIT_SUCCESS);

66
67

}

68
69

void print(const std::vector<int> &v) // 引数として渡す時はこのように書く

70{
for (auto i = v.begin(); i != v.end(); i++) {71

std::cout << *i << " ";

72

}

73

std::cout << std::endl;

74
75

}

$ ./8-2
010
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
0 10 20 30 40 50 60 70 80 90
0 1 2 3 4 5 6 7 8 9
13 : 0 1 2 3 4 5 6 7 8 9 11 12 13
9 : 0 1 2 7 8 9 11 12 13
可変長の大きさの配列を STL を使わずに実現しようとすると、動的なメモリ確保、解放の仕組みを活用して自分でプログラムを作成する必要があるが、STL を活用することで単純な関数や機能の呼び出しで簡潔に表現、実現できる。
次のプログラムは int, ﬂoat などの通常のデータ型でない class を対象としても vector 型のコンテナが作成、操作できることを示している。
★練習コンパイルして実行してみよう。
リスト 8.3: 8-3.cpp
12
34// g++ -Wall -ansi -std=c++11 -o 8-3 8-3.cpp#include<iostream>#include<cstdlib>#include<vector>(次のページに続く)

8.2. コンテナ163

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
5
6

const int SIZE = 10; // 可変長の配列の要素数

78class xyz { // ３次元の座標を表すクラス

9private:10

float x;11

float y;12

float z;13

public:14xyz(float tx = 0, float ty = 0, float tz = 0){x = tx; y = ty; z = tz;}

15

float getx(){return x;}

16

float gety(){return y;}
float getz(){return z;}

17
18

};

19
20

void print(std::vector<xyz> &v);

21
22
23

int main(void){

24

xyz tmp(1, 2, 3);25

std::vector<xyz> v(SIZE); // xyz 型のオブジェクトを要素とする vector26

for (int i = 0; i < SIZE; i++) {27

v.at(i) = tmp; // 配列の添字 i 番目の場所に値を代入する

28
29
30
31

}
// 次のように書いてもよい、同じ結果になる// for (auto it = v.begin(); it != v.end(); it++) *it = tmp;

32

print(v);3334

exit(EXIT_SUCCESS);

35
36

}

37
38

void print(std::vector<xyz> &v) // const を外したのはメンバ関数のため

39{
for (auto i = v.begin(); i != v.end(); i++) {40
41

std::cout << i -> getx() << " " <<42i -> gety() << " " <<43i -> getz() << std::endl;
}

44
45

}

$ ./8-3
1 2 3(次のページに続く)164

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0(前のページからの続き)

1 2 3
1 2 3
.
.
1 2 3
class のインスタンスを配列として作成することができ、更に要素数を簡単に増減できることがわかる。

8.3 イテレータ
イテレータは、コンテナに含まれるデータを順に取り出すために用意されている STL の機能である。イテレータにより取り出されたデータに、必要な処理を施して結果を得るのが一般的な処理の流れとなる。

vector と組み合わせて使われるイテレータは、配列の先頭から最後尾に向かって順に要素を取り出すものと、最後尾から先頭に向かって逆向きに取り出すものがある。

vector におけるイテレータの典型的な使い方は以下のようになる。
for (std::vector<int>::iterator it = v.begin(); it != v.end(); it++) {// *it で要素を参照できるので、ここに必要な処理を記述する}
繰り返し範囲の終端を表す end() は最後に処理するデータの次の要素を指すので注意する。これは for ルー
プを記述する時に、10 回の繰り返しであれば、for (int i = 0; i < 10; i++) {}と書くのと同じ考え方になる。このループ内部では、i は 0 から 9 まで変化するが 10 の時は処理されずループから抜けてしまう。つまり処理対象の 0--9 に対して、その次の値を終了条件に記述している。

8.4 アルゴリズム
アルゴリズムは、STL により提供されるデータ処理の関数を指す。アルゴリズムの各関数はコンテナやイテレータと組み合わせて使うことで、数多くの処理を簡潔な記述で実行できるように設計、作成されている。アルゴリズムには 100 種類を超える関数が用意されている。
次のプログラムは vector 型のコンテナに対して使用できる代表的なアルゴリズムの関数の使い方を示している。
★練習コンパイルして実行してみよう。

8.3. イテレータ

165

Robot Programming II Documentation, リリース 1.0

リスト 8.4: 8-4.cpp
1
2
3
4
5

// g++ -Wall -ansi -std=c++11 -o 8-4 8-4.cpp#include<iostream>#include<cstdlib>#include<vector>#include<algorithm> // アルゴリズムを使うときはこれが必要

67

const int SIZE = 10;89void print(const std::vector<int> &v);

10
11

int main(void)12{
std::vector<int> v(SIZE), v2(SIZE), v3(SIZE), v4(SIZE);

13
14
15

// アルゴリズムを使うことで、コンテナに対して様々な処理ができる

16
17
18
19
20
21
22
23
24
25
26

// fill(), 指定した値を配列の指定した範囲の要素に代入する
std::fill(v.begin(), v.end(), 0); // v 全体に 0 を代入する
print(v);
std::fill(v.begin(), v.begin() + v.size(), 1); // v 全体に 1 を代入する
print(v);
std::fill_n(v.begin() + 1, 5, 2); // v の途中の範囲に 2 を代入する
print(v);
std::fill_n(v.begin(), v.size(), 3); // v 全体に 3 を代入する
print(v);
std::cout << std::endl;

27
28
29
30
31
32
33
34
35
36
37
38
39

// copy(), 領域のコピー
std::fill(v.begin(), v.end(), 1); // 各コンテナの内容が区別できるように初期化
std::fill(v2.begin(), v2.end(), 2);
std::fill(v3.begin(), v3.end(), 3);
std::fill(v4.begin(), v4.end(), 0);
std::copy(v.begin(), v.end(), v4.begin()); // コピーの例 v → v4
print(v4);
std::copy(v2.begin(), v2.end() - 5, v4.begin() + 2);
print(v4);
std::copy_n(v3.begin(), 3, v4.begin() + 4);
print(v4);
std::cout << std::endl;

40
41
42
43

// swap(), 値の交換
std::fill(v.begin(), v.end(), 1);
std::fill(v4.begin(), v4.end(), 0);(次のページに続く)

166

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0(前のページからの続き)
44

std::iter_swap(v.begin() + 8, v4.begin() + 8); // 1 個交換する

45

std::swap_ranges(v.begin() + 1, v.end() - 5, v4.begin() + 1); // 複数個の交換

46

print(v);

47

print(v4);48

std::cout << std::endl;

49
50
51
52
53
54
55
56
57
58
59
60
61

// remove(), 指定した値を取り除く
std::fill(v4.begin() + 5, v4.end() - 1, 2);
print(v4);{
auto result = std::remove(v4.begin(), v4.end(), 1); // 値 1 の要素を除く
print(v4);
std::cout << v4.size() << std::endl;
v4.erase(result, v4.end()); // 必要な要素は先頭に集められるので、不要な部分を削除}
print(v4); // 結果だけが残っているのがわかる
std::cout << v4.size() << std::endl;
std::cout << std::endl;

62
63
64
65
66
67
68
69
70
71

// replace(), 指定した値を別の値で置き換える
v4.push_back(2);v4.push_back(3);v4.push_back(4);v4.push_back(5);print(v4);
replace(v4.begin(), v4.end(), 2, 7); // 値 2 の要素を 7 に書き換える
print(v4);
std::cout << std::endl;

72
73
74
75

// sort(), ソートする
std::sort(v4.begin(), v4.end()); // 値の小さい順に並べ替える
print(v4);

76
77
78
79
80

// unique(), ソート済の配列から重複した値を取り除く{
auto result = std::unique(v4.begin(), v4.end());
print(v4);

81
82
83
84
85
86
87

// 不要な値を削除する
v4.erase(result, v4.end());}
print(v4);
std::cout << v4.size() << std::endl;
std::cout << std::endl;

88(次のページに続く)

8.4. アルゴリズム

167

Robot Programming II Documentation, リリース 1.0(前のページからの続き)

exit(EXIT_SUCCESS);

89
90

}

91
92

void print(const std::vector<int> &v)

93{
for (auto i = v.begin(); i != v.end(); i++) {94

std::cout << *i << " ";

95
96

}

97

std::cout << std::endl;

98}$ ./8-4
0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1
1 2 2 2 2 2 1 1 1 1
3 3 3 3 3 3 3 3 3 3
1 1 1 1 1 1 1 1 1 1
1 1 2 2 2 2 2 1 1 1
1 1 2 2 3 3 3 1 1 1
1 0 0 0 0 1 1 1 0 1
0 1 1 1 1 0 0 0 1 0
0 1 1 1 1 2 2 2 2 0
0 2 2 2 2 0 2 2 2 010
0 2 2 2 2 0
6
0 2 2 2 2 0 2 3 4 5
0 7 7 7 7 0 7 3 4 5
0 0 3 4 5 7 7 7 7 7
0 3 4 5 7 7 7 7 7 7
0 3 4 5 7
5
データの代入、コピー、挿入、削除、移動、重複するデータの削除、ソートなど、プログラム作成時によく使われる代表的な処理が予め用意されているので、それらを１行の関数呼び出しで実行できる。前述したとおり、それらの処理を複雑なデータ構造を持つ class のオブジェクトに対しても適用できる。remove(),unique() については、最初の処理の後で配列後部に残っている不要な要素を削除する処理が必要になるので使い方を理解する必要がある。また、範囲の終端を表す end() は、イテレータの場合と同じく、最後に処理するデータの次の要素を指すので注意する。
次のプログラムは vector と配列の親和性を示している。vector は通常の配列と同じ操作ができるように実

168

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0
装されているので、vector に対して配列の要素をアクセスする記法が受け付けられる。また vector に対応
するアルゴリズムの関数は通常の配列に対しても使用することができる。
★練習
コンパイルして実行してみよう。
リスト 8.5: 8-5.cpp
1
2
3
4
5

// g++ -Wall -ansi -std=c++11 -o 8-5 8-5.cpp#include<iostream>#include<cstdlib>#include<vector>#include<algorithm>

6
7

const int SIZE = 10;89void print(const std::vector<int> &v); // vector 型コンテナの表示

10void print(int v[]);// 通常の配列の表示

1112

int main(void)

13

{

14

int v1[SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};15

std::vector<int> v2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};1617// vector は普通の配列と同じアクセスの仕方で操作可能

1819v1[0] = 99;20v2[0] = 99; // vector もこのように書いてよい

21

v2.at(1) = 99;

22
23

//
//

24
25

v2[SIZE+1] = 99; // 範囲外でもエラーにならず実行できてしまう
v2.at(SIZE+1) = 99; // at() を使うと範囲外はエラーで検出できる
print(v1);
print(v2);2627// アルゴリズムでは通常の配列も vector と同じように操作可能

28

30

std::fill(v1, v1 + 10, 1); // 通常の配列もこのようにして渡せる
print(v1);31

std::fill(v2.begin(), v2.end(), 2);32

print(v2);33

std::fill(&(v2[0]), &(v2[SIZE]), 3); // 無理やりポインタのように書いた

34

print(v2);2935

exit(EXIT_SUCCESS);

36
37

}

38

(次のページに続く)

8.4. アルゴリズム

169

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
39

void print(const std::vector<int> &v)

40{for (auto i = v.begin(); i != v.end(); i++) {41

std::cout << *i << " ";

42

}

43

std::cout << std::endl;

44
45

}

46
47

void print(int v[])

48

{

49

for (int i = 0; i < SIZE; i++) {50

std::cout << v[i] << " ";

51

}

52

std::cout << std::endl;

53}$ ./8-5
99 1 2 3 4 5 6 7 8 999 99 2 3 4 5 6 7 8 9
1 1 1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3 3 3

8.5 関数オブジェクト
関数オブジェクトは、オブジェクトそのものに関数の機能を持たせたもの、関数と同様に呼び出せるオブジェクトのことを指す。関数オブジェクトそのものは STL により提供される機能ではないが、アルゴリズ
ムの一部の機能が、関数オブジェクトにより処理内容を決められるように設計、実装されているため、STLと関係がある。関数オブジェクトを定義する方法はいくつかある。その一つとして、オブジェクトにおいて関数呼び出し演算子のオーバーロードを行う方法がある。この方法により定義される関数オブジェクト
は ファンクタ functor と呼ばれる。
関数オブジェクトと同様の機能で、C 言語でも使える 関数ポインタ という機能がある。関数オブジェクト
は、オブジェクト内にメンバ変数を持つことができるので、内部状態を記憶しておいて、それにより動作を制御することが可能で、関数ポインタを使うよりもより複雑な処理を実現しやすい。
以下は簡単な関数オブジェクトの例である。
★練習コンパイルして実行してみよう。

170

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0

リスト 8.6: 8-6.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 8-6 8-6.cpp#include<iostream>#include<cstdlib>

4
5class xyz { // ３次元の座標を表すクラス

6

private:7

float x;8

float y;9
10float z;public:xyz(float tx = 0, float ty = 0, float tz = 0){x = tx; y = ty; z = tz;}11

void operator () (void) { std::cout << x << " " << y << " " << z << std::endl; }

12
13
14

//
};

関数呼び出し演算子 () をオーバーロードして必要な動作を実現する

15
16

int main(void)17{
xyz tmp(1, 2, 3);

18
19
20
21
22

// tmp はオブジェクトであるが、() をオーバーロードして関数を呼び出す代わりに// 変数を表示する機能を定義した。() を指定するとその動作をさせることができる。
tmp();

23

exit(EXIT_SUCCESS);

24
25

}

$ ./8-6
1 2 3
この例のポイントは、クラスの定義途中の演算子 () の機能の定義をしている次の部分である。

void operator () (void) { std::cout << x << " " << y << " " << z << std::endl; }最初の () は機能を定義する演算子を表す。関数名に続けて書く (,) は普段は意識することはないが、+, -, =などと同じ演算子の一つであり、関数を呼び出す機能を持つ演算子である。ここではその演算子の機能を再定義することになる。次の (void) は、この演算子に渡す引数のリストを表している。今回は引数がないので void となる。以前に取り上げた演算子のオーバーロードの節における記述内容と見比べてほしい。その後の { ... } は、この演算子が実行された時の処理の内容を表す。今回はメンバ変数の値を表示するだけ
であるが、必要であればより複雑な処理を定義できる。

STL においてアルゴリズムの一部の関数は、関数オブジェクトにより動作を変更することができる。以下はその例で、ソートをする関数の大小関係の判定を関数オブジェクトにより変更している。class normal は小さい順に並べる判定をする関数オブジェクト, functor 、class reverse は大さい順に並べる判定をする関数
オブジェクト, functor となる。

8.5. 関数オブジェクト

171

Robot Programming II Documentation, リリース 1.0★練習コンパイルして実行してみよう。
リスト 8.7: 8-7.cpp
1
2
3
4
5

// g++ -Wall -ansi -std=c++11 -o 8-7 8-7.cpp#include<iostream>#include<cstdlib>#include<vector>#include<algorithm> // アルゴリズムを使うときはこれが必要

67
8class normal {public:bool operator()(int x, int y) { return x < y ? true : false; }

9
10
11

//
};

bool operator()(int x, int y) { return x < y }// こうしても同じ結果になる

12
13

class reverse {14

public:bool operator()(int x, int y) { return y < x ? true : false; }

15
16

};

17
18

void print(const std::vector<int> &v);

19
20

int main(void)

21

{

22

std::vector<int> v = {0, 1, 2, 8, 9, 3, 4, 5, 6, 7};23

normal n;

24

reverse r;

25
26

print(v);27

std::sort(v.begin(), v.end()); // 値の小さい順に並べ替える

28

print(v);2930

v.at(5) = 99;

31

print(v);32
33

std::sort(v.begin(), v.end(), n); // 値の小さい順に並べ替える

34

print(v);35

std::sort(v.begin(), v.end(), r); // 値の大きい順に並べ替える

36

print(v);37

std::cout << n(1, 2) << " " << r(1, 2) << std::endl; // こんな呼び出し方もできる38
39

exit(EXIT_SUCCESS);

40
41

}
(次のページに続く)172

第 8 章 標準テンプレートライブラリ、STL : Standard Template LibraryRobot Programming II Documentation, リリース 1.0(前のページからの続き)
42
43

void print(const std::vector<int> &v)

44{for (auto i = v.begin(); i != v.end(); i++) {45

std::cout << *i << " ";

46
47

}

48

std::cout << std::endl;

49}$ ./8-7
0 1 2 8 9 3 4 5 6 7
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 99 6 7 8 9
0 1 2 3 4 6 7 8 9 99
99 9 8 7 6 4 3 2 1 0
1 0
プログラムの途中に以下のように 3 回 sort() が呼び出されている。

std::sort(v.begin(), v.end()); // 小さい順に並べ替え、デフォルトの動作
std::sort(v.begin(), v.end(), n); // 値の小さい順に並べ替える
std::sort(v.begin(), v.end(), r); // 値の大きい順に並べ替える
最初は並び替えの対象となる範囲だけを引数として指定しているが、この場合はデフォルトの動作で「値の小さい順に並べ替える」となる。2, 3 回目の呼び出しでは範囲の指定に加えて、3 番目の引数として並び替えのための判定を行う関数オブジェクト n, r が指定されている。この場合 sort() では並び替えの基準として n, r の返り値を利用する。n, r では大小判定の返り値が逆になっているので、sort() による並び替えの結果も逆順になっていることがわかる。関数オブジェクトによるこのような機能の指定により、アルゴリ
ズムの処理に独自の機能を追加することが可能となる。

8.6 まとめ
この章の目標• STL の概念、実現している機能を理解する。
• コンテナ、イテレータ、アルゴリズム、関数オブジェクトの概念、実現している機能を理解する。
• STL を活用してプログラムを作成できる。
練習問題

1. STL の機能を使わずに、通常の配列、for ループなどを組み合わせて、次の機能を持つプログラムを
C++ で作成しなさい。10 個の要素を持つ int の配列 a[], b[] を定義する。a[] を 0 で初期化する。a[]を b[] にコピーする。b[] を画面に表示する。(e8-1.cpp)

2. 前記の問題を STL の機能、コンテナ、イテレータ、アルゴリズムを活用して作成しなさい。(e8-2.cpp)

8.6. まとめ173

Robot Programming II Documentation, リリース 1.0
3. 前記の問題を using を使って std の名前空間を指定して作成しなさい。(e8-3.cpp)
4. STL の機能を使わずに、通常の配列、for ループなどを組み合わせて、次の機能を持つプログラムを
C++ で作成しなさい。int ＊ a に対して 10 個の要素を持つ配列を動的に確保して 0 で初期化する。int＊ b に対して 11 個の要素を持つ配列を動的に確保して配列 a を配列 b にコピーする。b の最後の要
素に 0 を代入する。a の領域を解放する。b の内容を画面に表示する。(e8-4.cpp)

5. 前記の問題を STL の機能、コンテナ、イテレータ、アルゴリズムを活用して作成しなさい。(e8-5.cpp)

174

第 8 章 標準テンプレートライブラリ、STL : Standard Template Library

175

第 9 章 まとめ、練習問題

これまで取り上げた内容を理解してプログラム作成時に活用できるか、練習問題を通して確認する。プロ
グラム作成に際して、以下を共通の条件とする。

• 変数の初期値の指定がされていない場合は適当な値を代入しておく。
• main() から呼び出す、と指示されている場合は、main() を含めてコンパイルに必要な記述を一とおり含める。実行時に結果の表示ができるようにする。

9.1 C++ の基本
1. 整数を 1 個 KBD (キーボード) から入力して、画面に表示する。cin, cout を使用する。(e91-1.cpp)
2. 文字列を 1 個 KBD から入力して、画面に表示する。cin, cout を使用する。(e91-2.cpp)
3. 上記のプログラムを using を指定して作成する。(e91-3.cpp)
4. 2 個の整数の加算を行い返り値とするグローバル関数 int f1(int a, int b) を作成する。名前空間 n1 を定義し、その中で 2 個の整数の減算を行い返り値とする関数 int f1(int a, int b) を作成する。main() から両者を呼び出す。(e91-4.cpp)

5. 上記のプログラムを n1 に対して using を指定して作成する。(e91-5.cpp)
6. 引数 a, b, c を小さい順に並び替える関数 void f2(int ＊ a, int ＊ b, int ＊ c) をポインタを使って作成する。並び替えた結果を小さい順に a, b, c に代入する。main() から呼び出す。(e91-6.cpp)

7. 引数 a, b, c を小さい順に並び替える関数 void f3(int &a, int &b, int &c) を参照を使って作成する。並び替えた結果を小さい順に a, b, c に代入する。main() から呼び出す。(e91-7.cpp)

8. new を使って int 10 個分の配列を動的に確保する。配列の要素に 1 から順に 1 ずつ増える値を代入して画面に表示する。その後配列を delete で解放する。(e91-8.cpp)

9. const を使って int の整数定数を 1 個定義し、画面に出力する。(e91-9.cpp)
10. int の配列中の最小値を返り値とする関数 int f4(int a[], int n) を作成する。ﬂoat の配列中の最小値を返り値とする関数 ﬂoat f4(ﬂoat a[], int n) を作成する。n は配列の要素数を表す。両者を main() から呼び出す。(e91-10.cpp)

11. 上記のプログラムを関数のテンプレートを利用して作成する。(e91-11.cpp)

Robot Programming II Documentation, リリース 1.0

9.2 class オブジェクト指向
下記の機能をまとめて publication.cpp として作成する。

1. 出版物を表すクラス publication を定義する。メンバ変数として string title; string author; int year; int
pages; を定義する。メンバ変数は private, メンバ関数は public のアクセス指定とする。(publication.cpp)
2. 上記の publication のコンストラクタを作成する。デフォルト引数として string 型は "none", int は 0を代入する。main() から呼び出す。

3. 上記の publication のデストラクタを作成する。本のタイトルと破棄されたことを示すメッセージを表示する。 main() から呼び出す。

4. 上記の publication において、string title; の入出力を行うメンバ関数 void SetTitle(string str), void GetTitle(string& str) を作成する。main() から呼び出す。
5. 上記の publication において、メンバ関数 void print() を作成する。出版物の情報を表示する。メンバ
変数の参照時は this ポインタを使用する。
下記の機能をまとめて complex.cpp として作成する。

1. 複素数を表すクラス mycomplex を定義する。メンバ変数として実部を表す ﬂoat re; と虚部を表す ﬂoat
im; を定義する。メンバ変数は private, メンバ関数は public のアクセス指定とする。(complex.cpp)
2. 上記の mycomplex のコンストラクタを作成する。デフォルト引数として re, im に 0 を代入する。
main() から呼び出す。
3. 上記の mycomplex において、re, im の入出力を行うメンバ関数 void set(ﬂoat r, ﬂoat i), void get(ﬂoat&r, ﬂoat& i) を作成する。main() から呼び出す。
4. 上記の mycomplex において、メンバ関数 void print() を作成する。"1 + 2i" のように表示する。
5. 上記の mycomplex において、演算子 = の動作を定義する。main() から呼び出す。
6. 上記の mycomplex において、演算子 + の動作を定義する。main() から呼び出す。
7. 上記の mycomplex において、演算子 ＊ の動作を定義する。main() から呼び出す。

9.3 class 継承
下記の機能をまとめて publication2.cpp として作成する。

1. 前節の出版物を表すクラス publication において、publication を継承する派生クラスにだけメンバ変数を公開するようにアクセス指定を変更しなさい。(publication2.cpp)
2. publication の派生クラスとしてクラス dictionary を定義しなさい。メンバ変数として string language;を定義しなさい。

3. dictionary のコンストラクタを定義しなさい。language の初期値として "Japanese" を設定しなさい。
4. dictionary において language の入出力を行うメンバ関数 void SetLang(string l), void GetLang(string& l)を作成する。main() から呼び出す。

5. dictionary のメンバ変数をすべて表示するメンバ関数 print() を定義しなさい。main() から呼び出す。

176

第 9 章 まとめ、練習問題

Robot Programming II Documentation, リリース 1.0
6. publication の派生クラスとしてクラス magazine を定義しなさい。メンバ変数として int month; を定義しなさい。

7. magazine のコンストラクタを定義しなさい。month の初期値として 0 を設定しなさい。
8. magazine において month の入出力を行うメンバ関数 void SetMonth(int m), void GetMonth(int& m) を作成する。main() から呼び出す。

9. magazine のメンバ変数をすべて表示するメンバ関数 print() を定義しなさい。main() から呼び出す。
10. publication の派生クラスとしてクラス textbook を定義しなさい。メンバ変数として string subject; を定義しなさい。

11. textbook のコンストラクタを定義しなさい。subject の初期値として "Robotics" を設定しなさい。
12. textbook において subject の入出力を行うメンバ関数 void SetSbj(string s), void GetSbj(string& s) を作成する。main() から呼び出す。

13. textbook のメンバ変数をすべて表示するメンバ関数 print() を定義しなさい。main() から呼び出す。

9.4 STL
下記の機能をまとめて stlvec.cpp として作成する。

1. 10 個の int を要素とする vector 型のコンテナを作成しなさい。(stlvec.cpp)
2. イテレータを使用して、適当な値を代入しなさい。すべての要素を表示しなさい。
3. 要素を 3 個追加しなさい。すべての要素を表示しなさい。
4. 要素を 2 個削除しなさい。すべての要素を表示しなさい。
5. ﬁll() を使用して配列の一部に同じ値を代入しなさい。すべての要素を表示しなさい。
6. replace() を使用して一部の値を変更しなさい。すべての要素を表示しなさい。
7. sort() を使用して値の小さい順に並べ替えなさい。すべての要素を表示しなさい。

9.4. STL

177

179

第 10 章QandA, FAQ, トラブルシューティング

質問する時の留意事項
設定やプログラムで、何かエラーが発生して動かなくなった時は、

1. エラーメッセージはなるべく画面の写真やキャプチャした画像ではなく、文字列としてテキストファ
イルにコピーして、それを添付する。

2. ターミナル上で$ history > history.txt を実行して、それまでに実行したコマンドの履歴をファイルに記録して、その history.txt をメールに添付する。

3. 書き換えたファイルがあれば、その変更点、またはファイル全体でもよいのでメールに添付する。
以上の情報があると早期に解決策を見つけるのに役立ちます。

10.1 履修登録、受講環境
101. --• (問題) 履修登録をしたけれど、Google Classroom の招待のメールが届きません。
(対処法) Google Classroom の招待のメールはトラネットの er?????-????@sti.chubu.ac.jp のアドレスではなくて、Google Classroom 用の er?????-????@gs4e.chubu.ac.jp のアドレスに届きます。GMail から確認してみて下さい。

102. --• (問題) 履修申告の時期が遅かったので、Google Classroom の招待のメールが届いていません。
(対処法) 受講する学生の名簿は 1 回目の講義の直前に確認して、必要な連絡等をしますが、その後はそれ程頻繁に名簿を確認するわけではないので、後から履修申告した学生に気がつかないことがあります。必要な連絡等がされない場合は、科目の担当教員に早めに連絡して下さい。

103. --• (問題) Google Classroom で公開されている講義の動画が、アクセス権がなくて視聴できません。
(対処法) 講義の動画は、Google Classroom のアカウント (er?????@gs4e.chubu.ac.jp) からでないとア
クセスできません。

104. --• (問題) 私の PC に内蔵されている無線 LAN のデバイスは、Ubuntu では認識されなくて使うことができないのですが、どうしたらよいですか？

Robot Programming II Documentation, リリース 1.0(対処法) 実習をする時に無線 LAN に接続されている必要はないので、若干不便ですが LAN を使う
時間と実習で Ubuntu を使う時間を分ければ問題ありません。以下の対応が考えられます。また、市販の USB 接続の無線 LAN アダプタで Ubuntu で使えるものが判明すれば、随時紹介します。
・有線 LAN で接続する。
・Windows で必要なファイルをダウンロードしておいて USB メモリなどを使って Ubuntu にデータを移す。
・動画の視聴はスマートフォンなどで行い、実習は Ubuntu で行う。
・学科が所有する USB 接続の無線 LAN アダプタが若干数あるので、貸し出すことが可能です。ただし個数が限られているので、希望者が多い場合、全員に貸し出せる
わけではありません。

105. --• (問題) 以前の講義で Ubuntu を使ったときに、自分のノート PC は内臓無線 LAN デバイスが認識されなかったので、学科から USB の無線 LAN デバイスを借りていました。この講義でも Ubuntu を使うと同じ問題が生じますがどうすればいいですか？

(対処法) 講義や実習の時に Wiﬁ が繋がらなくても受講は可能です。講義の時に必ず Ubuntu から Wiﬁに繋がっている必要はないです。ただし前後にいろいろ手間がかかって面倒になるとは思います。講義で必要な作業は以下になります。
動画や講義資料は画面が小さくなりますがスマホやタブレットでも見られるでしょう。サンプルプ
ログラムは事前にダウンロードしておけばよいので、Windows やスマホでダウンロードしておいたものを Ubuntu に移せばよいでしょう。作ったプログラムは Windows に移して、そこでレポートを書いて出せばよいでしょう。よって対処法としては以下が考えられます。

Ubuntu で認識する USB の無線 LAN デバイスについては、わかった時点で随時知らせます。
(講義で必要な作業)
・Google Classroom から動画を視る
・講義資料の Web ページまたは PDF ファイルを見る
・サンプルプログラムや自分で作ったプログラムを動かしてみる・レポートを提出する(考えられる対応)
・多少手間がかかるけれど、上記のように Windows やスマホを併用する
・有線 LAN を使う
・Ubuntu で認識する USB の無線 LAN デバイスを調べて購入する

180

第 10 章 QandA, FAQ, トラブルシューティング

Robot Programming II Documentation, リリース 1.0

10.2 講義資料、サンプルプログラム
201. --• (問題) Windows でソースファイルをまとめたファイル (＊.tgz) をダウンロードしようとしてリンクをクリックしたら、エラーメッセージが出てきてダウンロードできません。

(対処法) ソースファイルをまとめたファイル (＊.tgz) は、Ubuntu 上で gcc, g++ を使ってコンパイルするので、Windows でダウンロードしても使えません。Ubuntu からダウンロード、展開して下さい。
拡張子が .tgz のファイルの展開方法は講義資料に記載されています。GUI, CUI どちらの環境でも展開できます。GUI ならダブルクリック、CUI なら以下のコマンドになります。$ tar zxvf ﬁle.tgz

202. --• (問題) Google Classroom で公開されているサンプルプログラムのソースファイルのパッケージをダウンロードしようとしてリンクをクリックしましたが、ダウンロード後に利用できません。

(対処法) 拡張子 .tgz や .zip のファイルは複数のファイルを圧縮して固めたものが一つのファイルになっているので、これらのファイルはダウンロードしただけではダメで、展開する必要があります。
ダウンロードした後、ファイルの一覧が表示されて一見そのまま使えそうに見えますが、その段階ではまだ使えなくて、展開の処理を指示する必要があります。確実にファイルが展開されているか確認
してみて下さい。

10.3 Ubuntu
301. --• (問題) Ubuntu が壊れて起動しなくなったので実習ができません。
(対処法) 自力で Ubuntu がインストールできる場合は再インストールして下さい。自力ではできない
場合はサポートしますので、教員や TA に連絡して下さい。

302. --• (問題) 自分のノート PC で使っていた Ubuntu が壊れたので、再度インストールしましたが、gcc, g++などのツールが入っていません。

(対処法) $ sudo apt install build-essential を実行してインストールしてください。
303. --• (問題) 自分の PC には Ubuntu 20.04 が入っていますが、この環境でプログラミングの実習に取り組んで大丈夫ですか？
(対処法) Ubuntu20.04 で課題を行った場合の結果で大丈夫、問題ありません。この科目は標準的なC++ を扱う講義で特別なことはしないので、Ubuntu の version に依存することはほとんどありません。ただ、Ubuntu の version が変わるとコンパイラの version も変わることがあるので、ごく稀に挙動や結果が変わることがあります（過去数年で一度だけありました）。Ubuntu の version の違いで気がついたことががあれば、今後の参考までに教えて下さい。

304. ---

10.2. 講義資料、サンプルプログラム

181

Robot Programming II Documentation, リリース 1.0• (問題) 自分の PC は、Virtual Box 上で Ubuntu を動かしていますが、その環境で実習をしても大丈夫ですか？

(対処法) 大丈夫、問題ありません。

10.4 コンパイル、デバッグ
401. --• (問題) サンプルプログラム 2-4.c について、プログラム中に三角関数が含まれるため、-lm をコンパ
イル時に指定しなければならないと講義資料にありましたが、実際に試したみたところ、添付ファイルのように、-lm なし でも実行することができました。三角関数が含まれる場合でも、-lm が必要な
場合と必要でない場合があるのですか？ なお、サンプルプログラム 2-6.c は -lm を指定しなければ、コンパイル時にエラーが起きました。

(対処法) -lm を付けなくてもコンパイルが通るというのは初めて見ました。推測ですが、gcc と C++用のコンパイラ g++ は実体は同じもので、呼び出し方でどの言語をコンパイルするかが決まります。

C++ では -lm を付けなくてもコンパイルが通るので、gcc として呼び出したときも場合によっては必要なライブラリを自動で見つけているのかもしれません。ただ 2-6.c は通らないということはダメな時もあるということですね。

402. --• (問題) 3 章の練習問題の 3-24.c の間違いを見つける問題で、講義資料のとおりにデバッガを使用しましたが、うまくエラー箇所が検出されません。

(対処法) 結果で表示される inf という値は無限大を表す間違った値です。よって計算過程のどこかが間違っています。文法の間違いではないのでデバッガは間違いを見つけません。この問題を考える
ときは、計算の途中で変数の値を表示させてみて人が間違いを見つける必要があります。計算の途中で各変数は本来どういう値であるべきかを考えて、それと異なるものを見つけるのがヒントです。

10.5 レポート
501. --• (問題) 課題のレポートを提出する時のファイルの形式は決まっていますか？
(対処法) PDF にして下さい。時々他の形式（Word など）でそのまま提出する人がいますが、見るときにそのフォーマット用のアプリケーションを別に起動しないといけないので余分な手間がかかります。

502. --• (問題) 毎週の課題の提出の仕方ですが、それぞれの問に対して Word で何らかの回答を書いていけばよろしいですか？提出方法や形式を教えていただきたいです。

(対処法) Word で書いて問題ありません、他のプログラムで書いても構いません。それぞれの問いの解答を書くのと、プログラムを作りなさいという問いに対しては答えとして作ったプログラムのソー
スリストを文書中に含めて下さい。提出するファイルの形式は PDF にしてください。提出するファ

182

第 10 章 QandA, FAQ, トラブルシューティング

Robot Programming II Documentation, リリース 1.0
イルの名前は「学籍番号_週.pdf」（例

er19099_1.pdf）で統一しますので、適当な名前を付けずに命名規則に沿って名前を付けて下さい。

503. --• (問題) 課題の結果をレポートに含めるときに、スクリーンショットなどでの画像添付でもいいですか？
(対処法) 画像添付ではそこに含まれる情報が再利用できないので、文字データの方がよいです。課題
では作成したプログラムのソースファイルを添付する機会が多くありますが、画像だと動作を試すことができません。採点する時に、文字データならファイルにコピーしてコンパイル、実行を手元の PC で簡単に試すことができます。もとが文字データの場合は画像としてではなく、文字としてレポートに含める、またはファイルを添付するのがよいです。

504. --• (問題) 何かを調べる課題では、調べる為のコマンドのみレポートにすればよいのか、また、そのコマンドにおける出力結果までレポートに貼り付けなければいけませんか？

(対処法) 調べる為のコマンドと、出力結果で問題に関係する部分の両方がある方がよいです。結果の文字数があまりに多くて全体を含めるのが難しい場合は、ファイル中の当該部分の場所（何行目辺りか）の記述と、必要な部分のみを抜き出して（内容によっては１行でも可）含めればよいです。

10.6 成績601. --• (問題) 成績はどのように決まりますか？
(対処法) 対面で講義ができる学期は、小テストの結果、毎週の提出物とレポートの点数と、(中間試験、) 期末試験の点数を総合して評価が決まります。それぞれの要素をどのように重み付けして加算
するかは初回に説明します。レポート等の提出物を出さないとその点が付かないので、一部できない問題があってもとにかく出し続けることが大切です。内容が不十分であったり提出期限を過ぎると評価が下がりますが、出せば何らかの点数は付けられるので、とにかく出した方が良いです。

602. --• (問題) リモート講義の場合の成績はどのように決まりますか？
(対処法) リモートで講義をする学期は、期末試験がないので毎週の課題のレポートの点数を集計して評価します。週によって問題数の多寡がありますが、週毎の配点は基本的に同じにする予定です。ただし最終週の課題だけは配点を大きくする予定です。レポートを出さないとその週は点が付かないので、一部できない問題があってもとにかく出し続けることが大切です。提出期限を過ぎると評価が下がりますが、出せば何らかの点数は付けられるので、期限を過ぎてしまっても出した方が良いです。

10.6. 成績

183

185

第 11 章 練習問題の答え

準備中

187

第 12 章 マークアップのサンプル レベル１

12.1 マークアップのサンプル レベル２
12.1.1 サンプル レベル３
装飾のサンプル
太字 、 イタリック 、

図 12.1: 図のサンプル
本文中での参照は、図のサンプル 図 12.1 を御覧ください。
数式のサンプル ABC + ABC

ABC + AB + AC =ABC + AB(1) + A(1)C =例題 3-2. 例題のサンプルを示しなさい。
答 3-2.

答のサンプル

リストのサンプル
箇条書き• 順序回路の構成と動作原理が理解できる。
• ミーリー型順序回路とムーア型順序回路の違いがわかる。
• 状態遷移図、状態遷移表が理解できる、作成できる。
• 宇都宮市Robot Programming II Documentation, リリース 1.0• 那須塩原市• 真岡市• 2 次元座標、行列、分数• 乗物、スポーツ、時間
数字付きリスト

1. まさし2. みんみん
3. 夢餃子 (#を使うと、自動で数字が割り当てられます)
定義リスト餃子 宇都宮の名物として有名。餃子の像もある。静岡の浜松がライバル。
ジャズ 宇都宮はジャズの町としても売り出し中。楽器メーカーを多数抱える静岡の浜松がライバル
焼きそば 知る人ぞ知る宇都宮の名物。専門店多数。なぜかビニール袋で持ち帰る。
表のサンプル
入力

出力

A

0

1

A+0

A+1

A+A

A・0

A・1

A・A

0

0

1

0

1

0

0

0

0

1

0

1

1

1

1

0

1

1

これは 強調 のサンプルである。
例題 2-1. (13) 10 を 2 進数に変換しなさい。
答 2-1.

2 ) 13-----2 )6 ... 1-----2 ) 3 ... 0-----1 ... 1
13 10 で上付文字になる。
コードサンプルの表示
リスト 12.1: 2-2.cpp
12#include <cstdio>#include <cstdlib>

3(次のページに続く)188

第 12 章 マークアップのサンプル レベル１

Robot Programming II Documentation, リリース 1.0(前のページからの続き)
4class a {int a;

5
6

} b;

7
8

int main(void)

9

{
a c;

10
11

printf("Hello world.\n");12
13

exit(EXIT_SUCCESS);

14
15
16

//
}

exit(EXIT_FAILURE);

リスト 12.2: 2-2.cpp
12
3// g++ -Wall -ansi -std=c++11 -o 2-2 2-2.cpp#include <cstdio>#include <cstdlib>

4
5

int main(void)6{
printf("Hello world. (2-2.cpp)\n");7
8

exit(EXIT_SUCCESS);

9
10
11

//
}

exit(EXIT_FAILURE);

rst ファイルを読み込む。

12.1. マークアップのサンプル レベル２

189

191

第 13 章rst ファイルのサンプル

13.1 タイトル
箇条書きの例• コンパイラの機能と使い方• デバッグの方法、デバッガの使い方• C, C++, プログラム作成の基本スキル• C++の様々な機能• オブジェクト指向 1、class• オブジェクト指向 2、継承
• 標準テンプレートライブラリ, STL : Standard Template Library• 総合演習課題

13.2 まとめこの章の目標• このファイルで示す内容が理解できる。
• 様々なマークアップ言語から自分にあったものを選ぶことができる。
練習問題

1. rst ファイルの特徴を簡単に説明しなさい。
2. rst ファイルを自分で書いてみなさい。
文書ファイルをそのまま読み込む。

rst ファイルのサンプル========================================
タイトル---------------------------------------箇条書きの例(次のページに続く)

Robot Programming II Documentation, リリース 1.0(前のページからの続き)- コンパイラの機能と使い方- デバッグの方法、デバッガの使い方- C, C++, プログラム作成の基本スキル- C++の様々な機能- オブジェクト指向 1、class- オブジェクト指向 2、継承
- 標準テンプレートライブラリ, STL : Standard Template Library- 総合演習課題
まとめ---------------------------------------この章の目標- このファイルで示す内容が理解できる。
- 様々なマークアップ言語から自分にあったものを選ぶことができる。
練習問題#. rst ファイルの特徴を簡単に説明しなさい。
#. rst ファイルを自分で書いてみなさい。

192

第 13 章 rst ファイルのサンプル

193

第 14 章• genindex• modindex• search

Indices and tables