• 検索結果がありません。

1 1.1 *1 1. sep1.cpp main() sep.cpp separate() *1 GNOME KDE 3

N/A
N/A
Protected

Academic year: 2021

シェア "1 1.1 *1 1. sep1.cpp main() sep.cpp separate() *1 GNOME KDE 3"

Copied!
11
0
0

読み込み中.... (全文を見る)

全文

(1)

C/C++

勉強会

2007年5月28日

目次

1 分割コンパイル 2 1.1 前置き . . . 2 1.2 試しにやってみる . . . 2 1.3 名前とスコープ . . . 5 1.4 ヘッダファイル . . . 11

(2)

1

分割コンパイル

1.1 前置き プログラムのソースコードは複数のファイルへ分割して書くことができ、またそれぞれを 別々にコンパイルすることができる。これを分割コンパイルと呼ぶ。 中規模以上のプログラムを作る場合、単一のファイルに全てのソースを書くことはコード 管理上の厳しい制約となる。関数や反復、再帰を駆使して出来る限り簡潔にプログラムを 作ったとしても、ある規模以上のプロジェクトでは全体のソースコードは数万行以上にもの ぼり、プログラムのバグ修正や機能追加の必要が生まれた場合、この数万行から必要な修正 箇所を探し出すのは骨の折れる作業だ。また数万行規模のプログラムでは、使われる変数や 関数の名前も膨大な数となり、これも管理上の手間を増大させる問題になる。 ソースを複数のファイルへ分割することで、コードの管理を楽にすることができる。ま た、分割したソースファイルのそれぞれを別々にコンパイルし、修正の際は変更のあった ファイルだけをコンパイルし直すことで、コンパイルにかかる時間*1を短縮することがで きる。 1.2 試しにやってみる

以下はsep1.cppのmain()からsep2.cppのseparate()を呼ぶプログラム。

(3)

sep1.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 e x t e r n i n t s e p a r a t e (v o i d ) ; 6 7 i n t main (v o i d) 8 { 9 c o u t << s e p a r a t e ( ) << e n d l ; 10 } sep2.cpp 1 i n t s e p a r a t e (v o i d) 2 { 3 r e t u r n 1 2 3 4 5 6 7 8 9 ; 4 } コンパイルする時は、gccなら

g++ -o sep.exe sep1.cpp sep2.cpp

bcc32なら

bcc32 -esep.exe sep1.cpp sep2.cpp

などとすることで、コンソールへ「123456789」を出力するプログラム sep.exe が得ら れる。 1.2.1 extern 前回までに作ってきたプログラムとの大きな違いは、sep1.cppの5行目で、separate()を 定義する代わりにextern を使ってseparate()を宣言していることだ*2。因みに関数宣言 *2関数の宣言を特に「プロトタイプ」と呼ぶ。なお、C++ では関数の宣言は常にプロトタイプとなるが、C では引数の型を省略し て書いた場合に前方宣言となる。

(4)

の際、externは省略してもよい。*3コンパイラはsep1.cpp sep2.cppを別々にコンパイル

するため、sep1.cppをコンパイルする段階ではsep2.cppで定義されたseparate()が見えな い。ファイルの外で定義された関数を呼び出すためには、このようにその関数がファイル外 で定義されているということをあらかじめ宣言する必要がある。試しに5行目のseparate() の宣言を削除してコンパイラに食わせると、コンパイラは sep1.cpp のコンパイル時にエ ラーを吐く。また、externを使ってsep1のコンパイルを通しても、sep2.cppを併せてコン パイルしなければリンカがエラーを吐く。リンカについては後述。

1.2.2 重複定義

sep1.cpp でseparate()が定義されていなくても、ファイル外で定義されていることが宣

言され、かつ実際に別のファイル(sep2.cpp)でseparate()が定義されていれば、プログラ ムは正常にコンパイルされ、動作する。ではseparate()がsep1.cppとsep2.cpp の両方で 定義されている場合はどうなるのか? これには3つの場合が考えられる。 まず一つは、リンカがエラーを吐く場合。sep1.cppとsep2.cppはそれぞれ正常にコンパ イルされても、それらをリンクする際にはエラーが出る。同一の名前を持つ関数は複数の実 体を持ってはならない。 一方、C++には関数の多重定義(overload)という機能があり、同じ関数名でとる引数 の型が違う複数のバリエーションを作ることができる。多重定義された関数の場合はそれ ぞれが別々の関数として扱われるため、エラーにならない。 overload.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 v o i d h a g e (v o i d) 6 { 7 c o u t << ” v o i dh a g e ( ) ” << e n d l ; 8 } 9 10 v o i d h a g e (i n t i ) 11 { 12 c o u t << ” i n th a g e ( ) : i ” << e n d l ; *3変数の場合は通常宣言と定義が同時にされてしまうため、宣言だけであることを明示するには extern を付ける必要がある。

(5)

13 } 14 15 v o i d h a g e ( s t r i n g s ) 16 { 17 c o u t << ” s t r i n gh a g e ( ) : ” << s << e n d l ; 18 } 19 20 i n t main (v o i d) 21 { 22 h a g e ( ) ; // h a g e ( v o i d )が 呼 ば れ る 23 h a g e ( 1 ) ; // h a g e ( i n t )が 呼 ば れ る 24 h a g e (”すっとりんぐー”) ; // h a g e ( s t r i n g )が 呼 ば れ る 25 } 関数の多重定義についてはしばらく後にもう一度詳しく話す。 そして最後に、2つのseparate()がファイルスコープや名前空間によって区別して定義さ れている場合。これはコンパイル時とリンク時、実行時のどれでもエラーにならない。 1.3 名前とスコープ 通常、プログラムで使われる変数や関数は名前を持ち、それぞれの名前はその名前の有効 範囲、スコープを持つ。ある名前はその有効スコープ内からは参照できるが、その外からは 不可視となり、参照できない。あるスコープの中で同じ名前が複数の定義を持ってはなら ない。 1.3.1 大域 関数定義の外で定義した変数、および関数は大域(グローバル)スコープを持つ。大域で 定義された名前は、プログラム全体から参照することができる。 global1.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 e x t e r n v o i d h i g e (v o i d ) ;

(6)

6 7 i n t g i = 1 0 0 0 0 ; // 大 域 ( グ ロ ー バ ル ) 変 数 の 定 義 8 9 v o i d h o g e (v o i d) // 関 数 名 は 普 通 大 域 の ス コ ー プ を 持 つ 10 { 11 c o u t << ” h o g e ” << e n d l ; 12 } 13 14 i n t main (v o i d) 15 { 16 h o g e ( ) ; 17 h i g e ( ) ; 18 c o u t << g i << e n d l ; 19 } global2.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 e x t e r n i n t g i ; // g iの 宣 言 6 e x t e r n v o i d h o g e (v o i d ) ; // h o g e ( )の 宣 言 7 8 v o i d h i g e (v o i d) 9 { 10 h o g e ( ) ; 11 c o u t << g i << e n d l ; 12 g i −= 1234; 13 } グローバル変数はプログラムの全域から参照/変更できるため、思いも寄らない位置で変 更されている可能性がある。中規模以上のプログラムではどのファイルのどの関数がどの

(7)

グローバル変数を参照/変更しているか、を完全に把握するのは難しいことであるため、グ ローバル変数を安易に使うことは推奨されない。 global i.cpp 1 /∗ 2 グ ロ ー バ ル 変 数 の 邪 悪 な 使 わ れ 方 の 例 3 ∗/ 4 5 #i n c l u d e <i o s t r e a m > 6 7 u s i n g namespace s t d ; 8 9 i n t i ; // グ ロ ー バ ル 変 数 のi 10 11 // ま だ 定 義 し て な い け ど 宣 言 し と く だ け で も 関 数 は 呼 べ る 12 e x t e r n v o i d h o g e (v o i d ) ; // e x t e r n v o i d h o g e ( v o i d )と 同 じ 意 味 13 14 i n t main (v o i d) 15 { 16 /∗ 17 一 見0か ら9 9ま で の 数 字 を 出 力 し な が ら 18 h o g e ( )を 呼 び 出 し て い く ル ー プ だ が … … 19 ∗/ 20 f o r ( i =0; i <100; i ++) { 21 c o u t << i << e n d l ; 22 h o g e ( ) ; 23 } 24 } 25 26 v o i d h o g e (v o i d) 27 { 28 /∗ 29 こ こ で 破 綻 。 30 iを 変 更 し ち ゃ っ て る の で 、m a i n ( )の ル ー プ が 31 永 遠 に i1 0 0に 到 達 し な い 無 限 ル ー プ に 。

(8)

32 ∗/ 33 f o r ( i =0; i <10; i ++) { 34 c o u t << ” h a g e h a g e ” << e n d l ; 35 } 36 } // h o g e ( )の 定 義 が 別 フ ァ イ ル に 書 か れ た り す る と な お 悲 惨 1.3.2 ファイルスコープ 大域の変数や関数は通常 extern、つまりファイル外にも有効なスコープを持つものとし て宣言/定義されるが、staticを指定して宣言/定義することにより、スコープをファイル 内だけに制限することもできる。 static1.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 6 s t a t i c i n t g i ; // フ ァ イ ル ス コ ー プ 7 8 // 宣 言 の み 9 s t a t i c v o i d h i g e (v o i d ) ; // フ ァ イ ル ス コ ー プ 10 11 s t a t i c i n t h o g e (v o i d) // フ ァ イ ル ス コ ー プ 12 { 13 h i g e ( ) ; 14 r e t u r n g i ; 15 } 16 17 s t a t i c v o i d h i g e (v o i d) // フ ァ イ ル ス コ ー プ 18 { 19 g i ++; 20 } 21

(9)

22 i n t main (v o i d) 23 { 24 f o r (i n t i =0; i <100; i ++) { 25 c o u t << h o g e ( ) << e n d l ; 26 } 27 } static1.cppに以下のファイルを加えると、リンカがエラーを吐く。 static2.cpp 1 /∗ 2 フ ァ イ ル ス コ ー プ の 名 前 は 外 か ら は 見 え な い 3 ∗/ 4 5 e x t e r n i n t g i ; // 見 つ か ん ね 6 e x t e r n i n t h o g e (v o i d ) ; // 見 つ か ん ね 7 8 v o i d f o o b a r (v o i d) { 9 g i ++; // 見 つ か ん ね 10 h o g e ( ) ; // 見 つ か ん ね 11 } ファイルスコープの適切な利用は、プログラムのモジュール化を支援する。あるファイル を変更しても他のファイルを変更せずに済むようプログラムを書くには、ファイル間の依存 関係を弱くする必要がある。staticな変数を使うと、その変数の変更で影響を受けるのが同 じファイル内に制限されるため、依存関係を弱めることができる。 1.3.3 関数スコープ 関数の中で定義された名前は、その関数内に限定されたスコープを持つ。 fscope.cpp 1 v o i d f s c o p e (v o i d) 2 {

(10)

3 i n t i ; // 関 数 ス コ ー プ 4 t y p e d e f i n t u i n t 3 2 ; // 関 数 ス コ ー プ 5 } 1.3.4 ブロック 関数内のブロック(「」と「」で囲まれた領域)は独自のスコープを持つ。またifやwhile、 for、switchの条件部で定義された名前は、その本文内のスコープを持つ。 block.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 i n t main (v o i d) 6 { 7 s t r i n g s = ” h a g e ”; 8 i n t i= 2 0 0 0 ; 9 10 { 11 s t r i n g s = あいうえお”; 12 c o u t << s << e n d l ; 13 } // sの ス コ ー プ は こ こ ま で 14 15 f o r (i n t i =0; i <100; i ++) 16 c o u t << i << e n d l ; // iの ス コ ー プ こ こ だ け 17 18 f o r (i n t i =0; i <100; i ++) { // iの ス コ ー プ こ の 中 19 t y p e d e f i n t i n t 3 2 ; // こ の ブ ロ ッ ク 内 で の み 通 用 20 i n t 3 2 x =0; // こ の ブ ロ ッ ク 内 で の み 通 用 21 c o u t << i + x << e n d l ; 22 } 23 c o u t << s << e n d l ; // ” h a g e ” 24 c o u t << i << e n d l ; // 2000

(11)

25 } 1.4 ヘッダファイル プログラム内で全ての名前の宣言/定義は一貫性を持たなければならない。が、関数のプ ロトタイプやグローバル変数の宣言などを各ソースファイルへ分散させると、管理や修正の 手間が増大する。名前の宣言だけを共通のヘッダファイルへ隔離し、それぞれの定義を各 ソースファイルへ分割することで、全体の整合性を保ちやすくなる。 header1.cpp 1 #i n c l u d e ” h e a d e r . h ” 2 3 4 i n t main (v o i d) 5 { 6 h o g e ( ) ; 7 } header2.cpp 1 #i n c l u d e <i o s t r e a m > 2 #i n c l u d e ” h e a d e r . h ” 3 4 u s i n g namespace s t d ; 5 6 v o i d h o g e (v o i d) 7 { 8 c o u t << ほげー << e n d l ; 9 }

参照

関連したドキュメント

これはつまり十進法ではなく、一進法を用いて自然数を表記するということである。とは いえ数が大きくなると見にくくなるので、.. 0, 1,

―自まつげが伸びたかのようにまつげ 1 本 1 本をグンと伸ばし、上向きカ ールが 1 日中続く ※3. ※3

・少なくとも 1 か月間に 1 回以上、1 週間に 1

前掲 11‑1 表に候補者への言及行数の全言及行数に対する割合 ( 1 0 0 分 率)が掲載されている。

方針 3-1:エネルギーを通じた他都市との新たな交流の促進  方針 1-1:区民が楽しみながら続けられる省エネ対策の推進  テーマ 1 .

1 つの Cin に接続できるタイルの数は、 Cin − Cdrv 間 静電量の,計~によって決9されます。1つのCin に許される Cdrv への静電量は最”で 8 pF

QRされた .ino ファイルを Arduino に‚き1む ことで、 GUI |}した ƒ+どおりに Arduino を/‡((スタンドアローン})させるこ とができます。. 1)

導入以前は、油の全交換・廃棄 が約3日に1度の頻度で行われてい ましたが、導入以降は、約3カ月に