Previous ToC Next

1. C++入門(1)

本章では、簡単なプログラムを例として、「とりあえず C++ を使ってみる」ことを第一の目標にする。それから、 Fortran と対応さ せながら関数、制御構造などを見ていく。

 #include <iostream>
 using namespace std;
 int main()
 {
     double a, b, c;
     cin >>a >> b;
     c = a + b ;
     cout << "a+b=" << c << endl;
     return 0;
 }
上のプログラムは、簡単な C++ プログラムである。まずこのプログラ ムを動かしてみるということが目標である。まず、このプログラムはどういうも のかを説明しておく。

なお、同じことを Fortran で書くと多分こんな感じである。

      program sample
      real*8 a, b, c
      read(5,*) a, b
      c = a + b
      write(6,*)  'a+b=', c
      end

比べてみると、まあ、似ているところもあるし違うところもある。とりあえず 順番に見ていこう、、、、といいたいところだが、最初の2行はちょっと後回 し。

まず int main() である。これは、「プログラムの始まり」を示すものであるところは Fortran における program sample と変わらないが、細かくいうといろいろ違 う。 Fortran では、 program 文は実はなくてもよくて、プログラム文で始ま る、またはいきなり始まるプログラム単位(とはなに、、、というのは省略) が「メインプログラム」、つまり、プログラムの実行がそこから始まるもので あった。

つまり、 Fortran では、まずメインプログラムがあって、それがサブルーチ ンとか関数を呼び出すという形になっており、メインプログラム、サブルーチ ン、関数のどれであるかで書き方がすこしずつ違っていた。

C/C++ でも同じようにメインプログラムがあって、それがサブルーチンや関数 を呼び出していくわけだが、 Fortran と大きく違うのは「宣言のしかたに区 別がなくてみな関数の形で宣言する」ということである。メインプログラムは 「 main という名前の関数」であり、そういう名前をつけておくとリン カがここから実行を始めるようにしてくれる。逆に、main という名前の関数 がないとリンク時にエラーにになる。

関数とサブルーチンの違いは値を返すかどうかということだけなので、 C/C++ で は値を返さない関数も許すことで統一的な記法を可能にしている。

元に戻ると、 main関数の宣言は

  int main()
  {
      関数の本体
  }
という形になっている。ここで int は関数の型であり、ここでは整数 型ということになる。

C/C++ で使う基本的な型には以下のようなものがある

  型名   説明          Fortran との対応
  int    整数型        integer
  float  実数型        real (real*4)
  double 倍精度実数型  double precision (real*8)
  char   文字型        character

整数型では長さを指定出来る。このあたりから処理系依存になってくるが、 short, long, long long といったものが指定できる。さらに、符号ありかど うかを signed/unsigned とつけることで区別できる。したがって、 unsigned long long int と書くと非常に長い符号なし整数ということ になり、通常 64 bit の整数で0から までを表現で きることになる。

さて、メインプログラムが値を返しても受けとるところがないと思うかもしれ ないが、これは実は OS (というか、 UNIX/Linux/Windows の通常の環境ではプログラムを起 動したシェル)が受けとる。帰ってきた値によって、プログラムが正常に終了 したかどうかを判断したりするのにつかうわけである。

次に mainは関数の名前で、これはメインプログラムなら main でないといけない。それ以外では好きな名前をつけていいわけだが、 名前はアルファベットまたはアンダースコアで始まり、アルファベット、アン ダースコアまたは数字が続く。 Fortran 77 の規格では名前は 6 文字以下と いう制限があったが、 C/C++ では少なくとも 31 文字までは問題なく使える ことになっている。

その次の () は引数リストを書くためのものだが、今日のメイン関数は引数が ないので中身は空である。そのあとの中括弧 { から } までの間に

を書く。まあ、この辺はそう決めたからそう書くことになっているというだけ。

なお、 Fortran との違いとして、 改行や行内での位置が意味を持たないとい うことがある。 Fortran だと(少なくとも昔のでは)文は 7 カラム目から 72 カラム目までに書くとか 1-5カラムはコメント記号/文番号であるとか6カ ラム目は継続行マークとかいうのがあったが、 C/C++ ではその辺は全く気に する必要はない。長い式なら適当に改行して構わないし、 72 カラムをはみ出 したものが無視されるとかいうこともない。

そのかわり、変数名、キーワード中に空白をいれたりはできない。例えば Fortran では

      p r o g r a m sam ple
      wr ite(6,*)'Test'
      r e t u r n
      e n d
というようにプログラムの中に好きなように空白を入れることができたが、 C/C++ ではそんなことはできない。

なお、この「空白が無視される」という Fortran の仕様は割合問題が多いも のである。 DO 10 I = 1.10 が一体なんであると解釈されるか?を考えてみるとちょっ と面白い。

次は double a, b, c; である。これは a, b, c double型の変数であると宣言している。上に書いたように改行に特別な意味 がないので、宣言の終りをしめすためにセミコロン ``;'' をつける。これは 実行文でも同様である。 Fortran では変数は宣言しなくても使えたが、 C/C++ では必ず宣言しないといけない。これは、タイプミスによって妙なバグ が発生することを防げるので好ましいと思う。

なお、 Fortran でも C で関数やサブルーチンの中の変数宣言は先頭にまとまっ ている必要があったが、 C++ ではそんな必要はなく使うところより前であれ ばどこでも宣言できる。

次にくるのが

  cin >>a >> b;
であるが、これはキーボード(正確には「標準入力」)からの入力がまず変数 a、次に変数 b に格納される。基本的には Fortran の read 文と同じような ものである。細かいことをいい出すと無限にあるのでそのあたりは参考書を見ること。

次の c = a + b ; は最後にセミコロンがつくのを別にすれば Fortran と同じ。式の書き方、使える数学関数などは Fortran とさして変わ りはない。こまかな違いはいろいろあって、例えば

整数に対する多様な操作が提供されていることは、ハードウェア制御など、普 通には高級言語ではできないような操作を可能にする。これが C/C++ が広く 使われる理由の一部ではある。

さて、出力の

    cout << "a+b=" << c << endl;
をみてみよう。これも、とりあえずは Fortran の write 文と同じようなものと思っ ておいていい。文字列定数は一重ではなく二重の引用符で括る。いくつかのも のを並べて書くには << でつないでいく。改行には endl を書く。 これをつけないと行が変わらないので、その次に何か書くと同じ行につながっ て書かれる。Fortran では、特別な制御をしない限りwrite文では自動的に改 行が入ったが、 C/C++ ではそうではない。なお、実数の書式制御とかの細か い指定ももちろんできる。これも参考書のほうを見て欲しい。

最後は return 0; である。これは、この関数が 0 を返して終るとい うことになる。 0 の代わりにint 変数や整数値になる式を書けば、もちろん その値が戻る。

通常の UNIX シェルでは、この値が $status というシェル変数に格納 される。

1.1. 簡単な実習

まず、このプログラムをエディタで作成し、例えば example1.cpp といった名 前でセーブしてみよう。

ここで、いくつかの注意をしておく。

1.1.1. プログラムのコンパイルと実行

コンパイルとは、C++ で書いたプログラムを、計算機が実際に実行できる 形に翻訳する作業である。 これには g++ というコマンドを使う。

コンパイルは、シェルウインドウで

  g++ example1.cpp  -o example1

と入れる(例によって最後にリターンする)。すると example1 という名前の 実行ファイルができる。

1.1.2. プログラムの修正

多くの場合、入力したプログラムは実行前に「エラーです」のようなメッセー ジがでて止まってしまう。エラーの場所に応じてエディタでプログラムを修正 し、セーブしたあともう一回 g++ して見よう。

1.1.3. プログラムの実行

実行は、シェルウインドウで

   ./example1
と入れる。このあとリターンすると、キーボードから数字を入れるのを待ってい る状態になるので、数字をいれてはリターンするのを2回繰り返せばその2つの 和が表示されるはずである。

少しプログラムを修正して、もう少し芸のあるものにしてみよう。

   // program example 2
   #include <iostream>
   using namespace std;
   
   int main()
   {
       double a, b;
       cout <<"Enter numbers a and b:";
       cin >> a >> b ;
       cout << "a+b = " << a+b <<endl;
       cout << "a-b = " << a-b <<endl;
       cout << "a*b = " << a*b <<endl;
       cout << "a/b = " << a/b <<endl;
       return  0;
   }

最初の // ... はコメント(注釈)といわれるもので、コンパイラは // からその行の終りまでを無視する。このため、自分や他人 が後でもみて分かるようにするためのいろいろな説明などを書いておくこと ができる。

これは四則演算してみただけである。

1.2. 関数と制御構造

1.2.1. 値を返さない関数(手続き)

数学では「関数」といえば、指数関数とか三角関数のように、変数に対 応して値が決まるものだが、C++言語の場合は必ずしもそうではない。以下 の例で説明しよう。

    // procedure_sample
    #include <iostream>
    using namespace std;
    
    #define PI  3.14159265358979
    
    void print_volume(double radius)
    {
        cout <<"Radius = " << radius << endl;
        cout <<"Volume = " << radius*radius*radius*PI*4.0/3.0<<endl;
    }
    
    int main()
    {
        double x;
        cerr << "Enter radius : ";
        cin >> x;
        print_volume(x);
        return 0;
    }
このプログラムは、単に適当な数字を読み込んで、その値を半径とする球の体 積を表示するプログラムである。このプログラムでは、実際に体積を計算して 答を表示するのを、 print_volume という名前の関数が行なってい る。

この「関数」は英語の function の訳語であるが、数学的な「関数」と いうより、機能とか働きとかいった意味合いに近い。ただし、すぐあと で説明するように、値を返す関数というものもあり、こちらは数学的な 意味での関数に少し似ている。

値を返さない関数は、

  void 名前(型 引数1 [,引数2, ...] [, 型[ ... 引数i [,引数i+1,...]])
  {
      [変数宣言]
      実行部
  }

という形をとる。このような記述がプログラムのなかにあると、 もとのプログラム、つまり int main() で始まっているところの実行部のな かからここで新しく作った関数を「呼び出す」ことができる。Fortran では call とかがついたが、 C/C++ ではいきなり関数名を書くだけである。

1.2.2. 値を返す関数

   // bisection
   #include <iostream>
   using namespace std;
   
   double f(double x)
   {
       double y ;
       y = x*x*x - 2;
       return y;
   }
   
   void bisection(double &  xmin,
                  double &  xmax,
                  double eps)
   {
       
       double x, f_min, f_max;
       f_min = f(xmin);
       f_max = f(xmax);
       if (f_min * f_max  > 0.0){
           cout <<"cannot find solution...\n";
       }else{
           while(xmax - xmin > eps){
               x = (xmin + xmax) *0.5;
               if (f(x) * f_min > 0.0 ){
                   xmin = x;
               } else{
                   xmax = x;
               }
               cout << "x= " << x << " f(x)= "<< f(x) <<endl;
           }
       }
   }
   
   int main()
   {
       double x0,x1, eps;
       x0 = 0.0;
       x1 = 2.0;
       eps = 1e-10;
       bisection( x0, x1, eps);
       cout << "Final x = " << x0 << " " << x1 << endl;
       return 0;
   }

ここでは、「関数」らしく値を返すものを使ってみている。値を返さな いものとの違いは、

  void 名前(引数の宣言);
の代わりに
  型 名前(引数の宣言);
となることと、実行部の最後で、
  return 式;
の形の戻すべき値を指定することである。このようにして宣言した関数 は、 C++言語の標準のライブラリに入っている sin, cos, pow など の関数と全く同じように使うことができる。

関数では、値を一つしか返せない。したがって、上の例のように、二分 法で方程式を解いて、区間の両端の値を戻したければ、引数の形で返すことに なる。

とはいうものの、最初の例のところで書いたように、C++では普通に宣言 すると関数の引数の値はコピーされる。で、コピーされた方を書き換えても、元の値は 書き換わらない。元の変数の値を書き換えるためには、上の例のように引数の 宣言のところで型と変数名の間に & をつける。

これは、 C の場合とは大きく違うことに(Cを知っている人は)注意。 もちろん、 C と同じように書くこともできる。

C++ の場合、 & をつけた引数は Fortran の場合とおおむね同じよう に使える。

1.3. プログラムの説明

上のプログラムは、2分法で、方程式の(近似的な)解を求 めるものである。方程式は、関数=0 という形になっているものとしよう。

このやりかたでは、まず最初にどの範囲に答があるかは知っているものとする。 そうすると、図 1 にあるように、その範囲の両端で関数の符号が違っているはず である。

Figure 1:

その区間の中点で関数の値を計算する。図のように、中点での値と左端での値 の符号が同じなら、答えは中点と右端の間にある。この時は、中点の値で左端 の値を置き換える。逆に中点での値と左端での値の符号が違えば、もちろん答 えはその間にある。この時は右端の値を置き換える。いずれの場合でも、答が あるとわかっている区間の幅がもとの半分に狭まる。これを繰り返していって、 答をもとめる。

以下、関数 bisection の中身を見ていく。

1.3.1. 判断

  if (条件)  文1
  else 文2
という構造は、 条件が成り立っていれば文1を、そうでなければ文2を実行せよという意味にな る。文2がない(条件が成り立っている時はなにかするがそうでなければ何も しない)ときには

  if (条件) 文1
だけでいい。 なお、上の例では if (条件) の後ろが { 文 文 ... とつながっている。このまとまりのことを複文といい、一般に文が書けるところには複文も書け る。このため、 if (...) { .... }else { .... } というような風にすれば条件によって違ういくつかの処理をまとめてできる。

文とは何かをちゃんと説明してなかったが、 C/C++では式にセミコロンをつけ たものが文である。で、式はなにかというと、 代入 a=b+c といったものも 式、関数呼びだし bisection(xmin,xmax,eps) も式、単なる数式 a+b ももちろん式である。

C/C++言語の特徴として、実行されるものはすべて式であり、 (void であると いうことも含めて)値を持つということがある。代入式の値は代入された値そ れ自体なので、例えば a = b = c+d といったもので a と b の両方に 同じ値を代入できる。

条件は、数値同士の比較式(大小、等 しい)と、複数の比較式からできる論理式などが書ける。

具体的には、

  a >  b                 aがbより大きければ真
  a >= b                 aがbより小さくなければ真
  a <  b                 aがbより小さければ真
  a <= b                 aがbより大きくなければ真
  a == b                 aがbと等しければ真
  !条件                  条件が偽なら真(否定)
  (条件1 ) && (条件2)    両方真なら真(論理積、and)
  (条件1 ) || (条件2)    どちらかが真なら真(論理和、or)
というくらいがこれから出てくることがあるであろう。

ただし、実際に条件に書かれるものは値が整数になる式ならなんでもいい。さ らにおせっかいに、実数型の式でも勝手に整数に変換して評価してくれたりす る。こ のため、条件のところに a = b と書いても文法的には正しいが、大抵 の場合やってほしいこととはかなり違う意味を持つ。なお、処理系によってはこれに 警告を出してくれるものもある。

1.3.2. 反復

  while (条件){
      文
      ......
      文
  }
これは標準の Fortran 77 には対応するものがないが、非常に便利なものであ る。なお、 Fortran の DO ループに対応するものは for 文であり、

for (変数 = 最初の値; 変数 < 最後の値 + 1; 変数 ++ ) 文

という形に書くのが普通である。

これは、まず変数に最初の値を入れて文を実行し、次に変数を1増やしてまた文を 実行し、以下同様に繰り返して変数が最後の値になったらおしまいにするとい うことになる。このような繰り返し処理をループ処理という。この場合は Fortran の DO ループとほぼ同じ動作になる。

C 以外の多くのプログラム言語では、例えば do i = 1, 10 (Fortran の場合)というように、「変数を1増やしては同じことを繰り返す」 という上に書いた通りのことをするための特別な書き方(構文)があるが、 C/C++ の for を使った構文はもっとフレキシブルなものである。

 for(式1;条件式;式2){ 式3; 式4; ... }というふうに書いてあると、実際に
起きることは、

ということで、別に「変数に最初の値を入れて文を実行し、次に変数を 1増やしてまた文を実行し、」ということしかできないわけではない。例えば

  int i;
  for(i=0; i<262144; i *= 2){
     cout <<"i = " << i << endl;
  }
と書けば、変数iの値を繰り返しごとに2倍にすることになる。

なお、ここで、 i++; とか i *= 2; とかいうものが出 て来たが、これは C/C++ 言語に特有の書き方で、基本的には i++ は i = i + 1と 同じ意味だし、 i*= 2 は i = i * 2 と同じである。また、 i -- という表現 も使える。

一般に、あらゆる演算(加減乗除の他に、論理演算なども)について、

  i 演算記号=j
  と書くのは
  i  = i  演算記号 j
  と書くのと同じと思っていい。

1.4. 関数の宣言と「スコープルール」

以下に書くことは、必ずしも本質的ではないが実際にプログラムがどう 動くか、あるいはどうやって書くかを理解するには結構重要なことである。

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

これまでの例では、

  double f(x)
  {
  ...
  }
  void bisection(...)
  {
     ... = f(...);
  }
  int main()
  {
  ...
     bisection(...);
  }

といった風に、「使う関数はあらかじめその前に定義されている」とい う形になっている。で、例えば引数の対応が間違っているとコンパイラ がチェックしてくれる。これでうまくいくのはまあいいような気がするわけ だが、良く考えてみるとちょっと変である。

というのは、使うすべての関数をプログラ ムの中で定義できるわけではないからで ある。このために使っているのが、「プロトタイプ宣言」と呼ばれる機 能である。プロトタイプ宣言は、例えば以下のような形をしている。

 void bisection(double & xmin,
               double &  xmax,
               double eps);
つまり、関数の最初のところだけ書いて、その後に実行部をつけないで セミコロンを書いておしまいにしたものである。これは、「この名前の 関数はこういう引数をもらって、こういう値を返します」ということを コンパイラに教える役割をはたしている。

この、プロトタイプ宣言というものは、要するにある関数について、 「それが外からどう見えるか」を規定している。えらそうにいえば「イ ンターフェース」を決めているということもできる。

Fortran77 では、言語仕様の中には特にこのようなチェックについて規定してい るところはないので、引数の型や数が間違っていてもコンパイラはエラーを検 出してくれないことが多い。

また、 C 言語の場合、プロトタイプ宣言が間違っていた時にかならずしもそ れが発見されるとは限らなかった。 C++ の場合には引数が違うものは違う関 数になるので、間違ったプロトタイプ宣言があるとリンク時に失敗するので発 見できる。

これは C++ の重要な機能、「関数のオーバーロード」(多重定義)というも のの結果である。例えば Fortran では、

  subroutine foo(x)
  real x
  ...
  end
というものがあった時に、

  subroutine foo(x)
  integer x
  ...
  end
というふうに引数は違うが名前は同じ関数を定義することはできない。ところ が、C++ では

  void  foo(double x){}

  void  foo(int x){}
は別物として扱われる。

もちろん、だからといって必要もないのに同じ名前を使うことは混乱を招くの で避けるべきだし、また引数の型が違うだけで処理の内容が同じならばテンプ レート(多分後で説明する)を使って汎用の関数を作るべきであろう。

1.5. include, namespace, define

プロトタイプ宣言がでてきたついでに、最初にスキップした

  #include <iostream>
  using namespace std;
が何をしているかをみていく。

  // #include <iostream>
と、この行をコメントアウトしてみよう。

 g++ example1-include-commentout.cpp  
 example1-include-commentout.cpp: 関数 ‘int main()’ 内:
 example1-include-commentout.cpp:6:5: エラー: ‘cin’ was not declared in this scope
      cin >>a >> b;
     ^
 example1-include-commentout.cpp:8:5: エラー: ‘cout’ was not declared in this scope
      cout << "a+b=" << c << endl;
      ^
 example1-include-commentout.cpp:8:28: エラー: ‘endl’ was not declared in this scope
      cout << "a+b=" << c << endl;
                            ^
といったエラーがでる。つまり、

  #include <iostream>
によって、 cin, cout, endl といったものが何であるかがコンパイラにわかるようになる。ではこれは何をしているか、というと、計算機のどこかにある(どこかはコンパイラ のほうが知っている)場所にある iostream というファイルを読み込んで、 一緒にコンパイルしてもらう、というものである。つまり、プログラムとしては、この行が、 iostream というファイルの中身に一旦置き換わる。

なお、「置き換えた結果」をコンパイラに出力させることもできる。

  g++ -E example1.cpp
とすると、置き換えた結果が標準出力(この場合画面)にでる。

なお、あとででてくるが、

  #include "foo.h"
とすれば、カレントディレクトリの foo.h がインクルードされる。また、相対パスや絶対パスで指定することもできる。

(カレントディレクトリ、相対パス、絶対パスといった言葉の意味がわからない人はちゃんと調べてください)

次に、

  using namespace std;
である。まず、namespace 「名前空間」というものがある、という話から始める必要がある。この、 cin, cout といったものは C++ 言語仕様として提供することになっているものだが、他の追加の機能(「ライブラリ」)を、色々な人が使えるように提供するとか、 大きなプログラムを分担して開発するとかを考えると、こういった関数の名前が、ある人が作ったものと別の人が作ったものが同じになってしまうことがありえる。

それを(ある程度避けるのが、この namespace というもので、例えば cin, cout は、 std という「名前空間」の中で定義されている。これはどういうことかというと、 実際には、 cin とか cout とか書いただけではコンパイラはその存在を認識できなくて、 std::cin とか std::cout とか書く必要がある、ということである。

で、それは面倒だとか、 namespace がなかった時代に書かれたプログラムを大きく書換えないですね動かしたい、といった時に使うのがこの using namespace というもので、

  using namespace std;
と書いておけば、 std::cin と書かずに cin と書くだけでコンパイラはそれだとわかるようになる。

1.5.1. スコープルール

スコープというのは何かというと、例えば変数であれば、宣言した変数 をどこで使うことができるかということである。宣言の有効範囲という こともできる。例えば

  double f(x)
  {
      double y;
      ...
  }
  int main()
  {
      double y;
      .....
  }
というプログラムを考えてみる。ここでは、 f(x) の中の変数 y と、 main の中の変数 y は全く別物であって、例えば f(x) の中で y を書き 換えたらその結果が main に伝わったりはしない。物理的には、これは、 メモリの違う場所におかれるということを意味している。

こういうことができるのは、ある関数の中で宣言した変数は、その関数 のなかでだけ有効だからである。その関数以外の関数が、勝手に変数を 書き換えたりはできない。これは、不便なような気がするかもしれない が、大きなプログラムを作るという場合とか、たくさんの人が協力して プログラムを作る時とかには非常に重要な機能である。

ただし、抜け道も準備されている。例えば

  double y;
  double f(x)
  {
      ...
      y = ...
  }
  int main()
  {
      f(x) = ...
      cout << y << endl;
  }
といったように、「関数の外」で変数を宣言することもできる。この場 合には、変数宣言の下に出てくるどの関数でも、この変数を使うことが でき、それらはみな同じものである。したがって、上の例のように、fの 中でその変数に代入し、 main の中でその値を見れば、fで入れた値が出 てくることになる。

Fortran ではこのようなことを実現するのには COMMON BLOCK を使う必要があっ たが、 C/C++ では手軽に関数間で変数の共有ができる。これは利点でもあり 欠点となることもある。

今日はこれくらい。次回は C++ 文法事項の続き。

1.6. 練習

今日出たプログラムを実際に書くなり cut & paste するなりして動かしてみ ること。
Previous ToC Next