静岡理工科大学 菅沼ホーム C/C++ と Java 目次

C/C++ 概説

    1. 1.データ型
    2. 2.演算子
      1. A.算術演算子と代入演算子
      2. B.関係演算子,等値演算子,及び,論理演算子
      3. C.ビット演算子とシフト演算子
      4. D.アドレス演算子と間接演算子
    3. 3.制御文
      1. A.分岐
      2. B.繰り返し
    4. 4.配列とポインタ
      1. A.配列とポインタ
      2. B.new 演算子と delete 演算子
    5. 5.関数
      1. A.基本的な方法
      2. B.アドレスの引き渡し
      3. C.参照渡し
      4. D.配列の引き渡し
      5. E.ポインタを戻り値
      6. F.関数名の引き渡し
      7. G.main 関数
    6. 6.クラス定義
    7. 7.演算子のオーバーロード
    8. 8.ポインタとデストラクタ
    9. 9.継承
    10. 10.テンプレート
      1. A.関数テンプレート
      2. B.クラステンプレート
  1. データ型(「 C/C++ と Java 」の第3章及び第4章参照)
    型名           バイト数  値の範囲
    void             *       *
    char             1       -128 ~ 127
    unsigned char    1       0 ~ 255
    short            2       -32,768 ~ 32,767
    unsigned short   2       0 ~ 65,535
    int              *       システム依存
    unsigned int     *       システム依存
    long int         4       -2,147,483,648 ~ 2,147,483,647
    unsigned long    4       0 ~ 4,294,967,295
    long long int    8       -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
    float            4       3.4E±38(7 桁)
    double           8       1.7E±308(15 桁)
    long double      *       拡張精度,システム依存
    bool             1       true(1) or false(0),数値の 0 は false に,その他は true に変換される
    			
  2. 演算子(「 C/C++ と Java 」の第3章及び第4章参照)

    1. 算術演算子代入演算子
      + : 加算
      - : 減算
      * : 乗算
      / : 除算
      % : 余り(整数演算に対してだけ使用可能)
      = : 代入
      ++ : インクリメント演算子( 1 だけ増加)
      -- : デクリメント演算子( 1 だけ減少)
      				
    2. 関係演算子等値演算子,及び,論理演算子
      >  より大きい  a > b   式 a の値が式 b の値より大きいとき真
      <  より小さい  a < b   式 a の値が式 b の値より小さいとき真
      >= 以上     a >= b  式 a の値が式 b の値以上のとき真
      <= 以下     a <= b  式 a の値が式 b の値以下のとき真
      == 等しい    a == b  式 a の値と式 b の値が等しいとき真
      != 等しくない  a != b  式 a の値と式 b の値が等しくないとき真
      || 論理和  x || y  式 x が真か,または,式 y が真のとき真
      && 論理積  x && y  式 x が真で,かつ,式 y が真のとき真
      !  否定    ! x      式 x が偽のとき真
      				
    3. ビット演算子シフト演算子
      | 論理和      x | y   対応するビットのいずれかが 1 のとき真.
      & 論理積      x & y   対応するビットの双方が 1 のとき真
      ^ 排他的論理和 x ^ y   対応するビットが異なるのとき真
      ~ 1の補数     ~ x      ビット毎に 1 と 0 を反転する
      << 左にシフト  x << 3  3 ビット左にシフト.x を 23 倍することに相当.
      >> 右にシフト  x >> 3  3 ビット右にシフト.x を 23 で割ることに相当.
      				
    4. アドレス演算子間接演算子

        変数は,主記憶領域のある特定の場所に付けられた名前です.その場所(アドレス)を求めるために使用されるのがアドレス演算子です.アドレス演算子は「 & 」で表し,例えば,変数 value のアドレスを求めるためには,
      &value
      				
      と書きます.このアドレスをデータとして保存しておくためには,ポインタという特別な領域が必要です.この領域に付ける名前,つまり,ポインタ変数を定義するためには,例えば,int 型変数に対するポインタは,
      int *point;
      				
      と宣言します.変数 value が int 型であったとすると,
      point = &value;
      				
      という文により,変数 value のアドレスがポインタ変数 point に代入され,右図のような関係になります( 変数 value の値が 123,そのアドレスが 100 番地であった場合).

        ポインタ変数について,その変数に記憶されている値(右図では,100 )ではなく,その値が指す場所に記憶されている値(右図では,123 )を参照したい場合があります.このようなとき使用されるのが間接演算子です.間接演算子は「 * 」で表し,例えば,ポインタ変数 point が示すアドレスに入っている内容を変数 data に代入するためには,
      data = *point;
      				
      と書きます.右図の例では,変数 data に 123 が記憶されます.

  3. 制御文(「 C/C++ と Java 」の第5章参照)

    1. 分岐

      1. if 文
        if (論理式) {
        	文1(複数の文も可)
        }
        else {
        	文2(複数の文も可)
        }
        					
      2. else if 文
        if (論理式) {
        	文1(複数の文も可)
        }
        else if (論理式) {
        	文2(複数の文も可)
        }
          ・・・
        else {
        	文n(複数の文も可)
        }
        					
      3. switch 文
        switch (式) {
        	[case 定数式1 :]
        		[文1]
        	[case 定数式2 :]
        		[文2]
        	 ・・・・・
        	[default :]
        		[文n]
        }
        					
    2. 繰り返し

      1. for 文
        for (初期設定; 繰り返し条件; 後処理) {
        	文(複数の文も可)
        }
        					
      2. while 文
        while (繰り返し条件) {
        	文(複数の文も可)
        }
        					
      3. do while 文
        do {
        	文(複数の文も可)
        } while (繰り返し条件) ;
        					

  4. 配列とポインタ(「 C/C++ と Java 」の第6章参照)

    1. 配列とポインタ

        配列とポインタとの関係を明らかにするために,以下のようなプログラムについて考えてみます.
      #include <stdio.h>
      
      int main()
      {
      	int x[4] = {100, 200, 300, 400};
      	int *y = &x[0];
      	int *z = &x[2];
      
      	printf("%d %d\n", x[2], *(x+2));
      	printf("%d %d\n", y[2], *(y+2));
      	printf("%d %d\n", z[0], *z);
      
      	return 0;
      }
      				
        1 行目の配列宣言を行うと,右図に示すように,int 型の 4 つのデータを保存するための連続した領域が確保されます.ポインタとの関係を見たい場合,その領域の先頭を指すポインタ変数 x の存在を考えた方が分かりやすいと思います.実際,配列変数の 3 番目の要素を x[2],または,*(x+2),いずれの方法で参照しても同じ結果が得られます.ただし,配列変数 x は,下に述べるポインタ変数 y とは,y++ は許されるが x++ は許されないなど,多少異なります.

        変数 y に対しては,配列 x の先頭アドレスが代入されているため,変数 y は,変数 x と同じ場所を指し,その内容は全く同じになります.また,変数 z のように,配列 x の途中の位置を指すようにすれば,変数 z は,配列 x の部分配列となり,その要素数は 2 となります.

        以上の点から,上のプログラムにおける出力結果は,すべて 300 となります.

    2. new 演算子delete 演算子

        new 演算子は,データ型とその個数を与えることによって,例えば,
      int *x = new int [4];   // int x[4] とほとんど同じ
      				
      によって,int 型のデータを 4 つだけ記憶するのに必要な領域を確保し,その領域の先頭のアドレスを返します.そのため,変数 x は,通常の配列と同様の方法で参照可能となります.また,delete 演算子は,new 演算子によって確保された領域を解放します.意図的に解放しない限り,確保された領域はプログラム終了時まで存在しますので,必要な場合は,例えば,
      delete [] x;
      				
      のように,delete 演算子によって解放して下さい.ただし.C++ においては,ガーベッジコレクションが行われませんので,大きな領域を確保する new 演算子と delete 演算子を多数回繰り返しますと,ゴミがたまってしまい,問題になる場合があります.

  5. 関数(「 C/C++ と Java 」の第7章参照)

      関数は,一般的に,
    戻り値の型 関数名 ( 引数リスト ) { 処理の中身 }
    			
    のように記述します.関数内では,与えられた情報(引数リスト)に基づき何らかの処理を行い,その結果を戻り値( 1 つの値)として返します.関数名は,同じプログラム内で唯一の名前である必要がありますが,引数や戻り値が異なる場合は同じ名前の関数が存在しても構いません(関数名の多重定義(関数名のオーバーロード)).

    1. 基本的な方法

        関数にデータを渡し,その結果を得るためには様々な方法が考えられます.最も簡単な方法は,以下に示すプログラム例( 2 つのデータの和を求める関数)のように,引数としてデータ(厳密には,データのコピー)を渡し,戻り値として結果を受け取る方法です.この関数において,「 int b = 0 」という記述がありますが,これは,「 2 番目の引数が省略されたとき,その値を 0 とする」といった意味であり,デフォルト引数と呼びます.

      #include <stdio.h>
      
      int add(int a, int b = 0)
      {
      	return a + b;
      }
      
      int main()
      {
      	int x = 10, y = 20;
      
      	printf("和 = %d\n", add(x, y));
      	printf("和 = %d\n", add(x));
      
      	return 0;
      }
      				

    2. アドレスの引き渡し

        引数として渡されたデータは,その値がコピーされて渡されるため,関数内において引数(上の例における a や b )の値を変更しても,main 関数内の x や y の値はその影響を受けません.しかし,ポインタが引数である場合は,ポインタ自身がコピーされても,ポインタが指す場所は同じであるため,ポインタを介してその内容を変更することが可能です.従って,例えば,下に示すプログラム例のように,結果を引数内の変数で受け取ることも可能です.

      #include <stdio.h>
      
      void add(int a, int b, int *c)
      {
      	*c = a + b;
      }
      
      int main()
      {
      	int x = 10, y = 20, z;
      
      	add(x, y, &z);
      	printf("和 = %d\n", z);
      
      	return 0;
      }
      				

    3. 参照渡し

        関数への引数を参照型にすることによっても,上と同様のことが可能です.参照型変数の宣言方法は以下の通りです.
      データ型 &別名 = 式;
      				
      例えば,
      int &y = x;
      				
      と宣言することにより,参照型変数 y は変数 x の別名としてふるまい,
      x = 10;
      y = 10;
      				
      の 2 つの文は全く同じ意味になります.下の例においては,参照型変数を利用して結果を受け取っています.

      #include <stdio.h>
      
      void add(int a, int b, int &c)
      {
      	c = a + b;
      }
      
      int main()
      {
      	int x = 10, y = 20, z;
      
      	add(x, y, z);
      	printf("和 = %d\n", z);
      
      	return 0;
      }
      				

        関数では,後に述べるクラスのオブジェクトを引数とする場合もあります.そのような場合に対しても,データ,アドレス,参照渡しといった 3 つの方法が考えられます.しかし,コピーを渡す方法の場合,大きなオブジェクトに対してはコピーするための時間が掛かってしまいます.また,アドレスを渡す方法では,参照方法の問題(「 . 」ではなく「 -> 」を使用)や内容の変更を許さないようにする指定(「 const 」指定)ができないなどの問題があります.そこで,オブジェクトを引数とする場合は,参照渡しがよく使用されます.

    4. 配列の引き渡し

        以上の例においては,1 つの結果だけを求めていましたが,和と差のように,複数の結果を得たい場合はどのようにすればよいでしょうか.その一つの方法は,配列を利用することです.配列を引数にすることは,アドレスを引数にすることに相当し,先に述べたような意味から配列要素を関数内で変更できるからです.下の例では,和と差を配列に入れて返しています.

      #include <stdio.h>
      
      void add(int a, int b, int c[])   // void add(int a, int b, int *c) でも可能
      {
      	c[0] = a + b;
      	c[1] = a - b;
      }
      
      int main()
      {
      	int x = 10, y = 20;
      	int z[2];   // int *z = new int [2]; でも可能
      
      	add(x, y, z);
      	printf("和 = %d,  差 = %d\n", z[0], z[1]);
      
      	return 0;
      }
      				

    5. ポインタを戻り値

        上の例では,結果を引数として返していましたが,アドレスを使用すれば,以下に示す例のように,戻り値を利用して複数の結果を得ることが可能です.

      #include <stdio.h>
      
      int *add(int a, int b)
      {
      	int *c = new int [2];
      
      	c[0] = a + b;
      	c[1] = a - b;
      
      	return c;
      }
      
      int main()
      {
      	int x = 10, y = 20, *z;
      
      	z = add(x, y);
      	printf("和 = %d,  差 = %d\n", z[0], z[1]);
      
      	delete [] z;
      
      	return 0;
      }
      				

    6. 関数名の引き渡し

        関数名を引数とするには,基本的に,関数のアドレスを使用すれば可能です.例えば,
      int (*sub)(double, char *)
      				
      という記述は,sub が,double 及び char に対するポインタという 2 つの引数をもち,int を返す関数へのアドレスであることを表しています.下の例では,2 つの int 型の引数を持つ関数名を引数として和と積の計算を行っています.

      #include <stdio.h>
      
      int add(int a, int b)
      {
      	return a + b;
      }
      
      int mult(int a, int b)
      {
      	return a * b;
      }
      
      int cal(int x, int y, int (*sub)(int, int))
      {
      	return sub(x, y);
      }
      
      int main()
      {
      	printf("和 %d\n", cal(2, 3, add));
      	printf("積 %d\n", cal(2, 3, mult));
      
      	return 0;
      }
      				

    7. main 関数

        例えば,
      add 2 3
      				
      とキーボードから入力すると,2 つの値 2 と 3 の和を計算し,その結果をディスプレイに出力したいような場合が存在します(下に示すプログラム例).このような場合,2 や 3 が main 関数の引数とみなされます.ただし,main 関数の場合は,引数の受け渡し方が以下のように決まっています.
      int main ( int argc, char *argv[], char *envp[] )
      				
      ここで,各引数の意味は次のようになります.

      argc : コマンドラインからプログラムへ渡された引数の数を指定する整数.プログラム名も引数と見なされるので, argc は 1 以上の値となります.「 add 2 3 」のような場合は,3 となります.

      argv : null 文字で終わる文字列の配列. 最初の文字列 ( argv[0] ) はプログラム名で,その後に続く各文字列はコマンドラインからプログラムへ渡される引数(文字列)です.

      envp : 環境文字列の配列へのポインタ.

      #include <stdio.h>
      #include <stdlib.h>
      
      int main(int argc, char *argv[], char *envp[])
      {
      	int i1, k, sum = 0;
      /*
      	 引数の内容の出力
      */
      	printf("	 引数の数 %d\n",argc);
      	printf("	 プログラム名 %s\n",argv[0]);
      
      	for (i1 = 1; i1 < argc; i1++) {
      		k = atoi(argv[i1]);				  /* 文字を整数に変換 */
      		printf("     %d 番目の引数 %d\n", i1+1, k);
      		sum += k;
      	}
      /*
      	 結果の表示
      */
      	printf("結果=%d\n", sum);
      
      	return 0;
      }
      				

  6. クラス定義(「 C/C++ と Java 」の第10章参照)

      住所録を作成する際のように,氏名,住所,電話番号などをまとめて一つのデータとして扱いたい場合があります.数学の例で言えば,複素数などが相当します.複素数は,実数部と虚数部という 2 つの実数データの組からなっており,それらのデータは常に一緒に扱われます.C の範囲において,複数のデータをまとめて扱う方法に構造体があります.

      クラスは,構造体を拡張した概念ですが,その内部に定義できるのはデータ(メンバー変数)だけではなく,そのデータを処理するための関数(メンバー関数など)も定義できます.そして,メンバー変数やメンバー関数に対して参照制限を設けることが可能です.構造体のデータのようにどこからでも参照できる変数・関数やメンバー関数だけから参照可能な変数・関数を設定することが可能です.クラスの定義方法を一般的に記述すれば以下のようになります(この例では,クラス名を Example1 としている).

    class Example1 {
    		int x;                // プライベートメンバー変数
    		 ・・・				  // メンバー関数だけから参照可能なメンバー変数
    	protected:
    		int y;                // 派生クラスだけから参照可能なメンバー変数
    		 ・・・
    	public:
    		int z;                // パブリックメンバー変数
    		 ・・・				  // いずれの関数からも参照可能なメンバー変数
    	private:
    		double fun1(int);     // プライベートメンバー関数
    		 ・・・				  // メンバー関数だけから参照可能なメンバー関数
    	protected:
    		double fun2(int);     // 派生クラスだけから参照可能なメンバー関数
    		 ・・・
    	public:
    		double fun3(int);     // パブリックメンバー関数
    		 ・・・				  // いずれの関数からも参照可能なメンバー関数
    	friend double fun4(int); // フレンド関数(通常の関数であるが,メンバー関数と同じ参照権限を持つ)
    	 ・・・
    	friend class Example2;   // フレンドクラス(クラス Example1 と同じ参照権限を持つ)
    	 ・・・
    };
    			

      ただし,上の例における public,private 等に対応するすべてのメンバーを所有する必要はありません.public 等を記述しないと,クラス宣言のメンバーがプライベートとみなされる点以外,記述する順番も一般的には決まっていません.また,関数の本体は,クラス定義の中に記述することも,クラス定義の本体では関数の宣言だけを行いクラス宣言の外側に記述することも可能です.

      以下に示すのは,実数部と虚数部からなる複素数を扱うためのクラスです.

    #include <stdio.h>
    
    class Complex {
    	private:
    		double real;
    		double imaginary;
    	public:
    					// 引数のないコンストラクタ
    		Complex() {};
    					// 引数のあるコンストラクタ
    		Complex(double a, double b = 0.0)
    		{
    			real      = a;
    			imaginary = b;
    		}
    					// 値の設定するためのメンバー関数
    		void set(double a, double b = 0.0);
    					// 出力するためのメンバー関数
    		void print();
    };
    
    void Complex::set(double a, double b)
    {
    	real      = a;
    	imaginary = b;
    }
    
    void Complex::print()
    {
    	printf("実数部 = %f, 虚数部 = %f\n", real, imaginary);
    }
    
    int main()
    {
    					// 初期設定を行う場合
    	Complex x(1.0);
    	x.print();
    					// 初期設定を行わない場合
    	Complex y;
    	y.set(5.1, 1.5);   // real は private であるため,y.real = 5.1; などは不可能
    	y.print();
    					// 初期設定を行う場合(ポインター)
    	Complex *z = new Complex (1.0, 2.0);
    	z->print();
    
    	return 0;
    }
    			

      ここで,コンストラクタとは,クラスのインスタンスが生成されたとき( Complex 型の変数が定義されたとき),最初に呼ばれる関数です.初期設定を行う関数と言って良いかもしれません.この例では,引数の無い場合とある場合に対する 2 種類のコンストラクタを定義しています.なお,メンバー関数 set や print を,クラスの定義内で関数の定義だけを行い,その本体をクラス定義の外に記述しています(コンストラクタに対しても可能).

  7. 演算子のオーバーロード(「 C/C++ と Java 」の第11章参照)

      複素数どうしの加算は,実数部どうし,及び,虚数部どうしの加算によって定義されます.従って,複素数どうしの加算を行うためには,例えば,加算を行うメンバー関数を定義してやる必要があります.しかし,x,y,z を上で述べた Complex 型のオブジェクトとした場合,通常の数値の演算のように,
    z = x + y;
    			
    のような記述ができればこれに越したことはありません.このことを実現可能にするのが演算子のオーバーロードです.演算子のオーバーロード機能を利用すれば,たとえば,+ 演算子の場合,
    operator+
    			
    というメンバー関数を定義してやれば,例えば,
    z = x + y;
    			
    という演算は,
    z = x.(operator+)(y);
    			
    と解釈され,希望の結果が得られます..

      以下に示す例は,Complex クラスに対して,+ 演算子のオーバーロードによって複素数の加算を実現しています.なお,この例においては,メンバー関数ではなく,フレンド関数を利用しています.その理由は,メンバー関数を利用すると,x が Complex 型でない場合,「 x.(operator+)(y) 」を解釈できず,エラーになってしまうからです.

    #include <stdio.h>
    
    class Complex {
    	private:
    		double real;
    		double imaginary;
    	public:
    					// 引数のないコンストラクタ
    		Complex() {};
    					// 引数のあるコンストラクタ
    		Complex(double a, double b = 0.0)
    		{
    			real      = a;
    			imaginary = b;
    		}
    					// 値の設定するためのメンバー関数
    		void set(double a, double b = 0.0);
    					// 出力するためのメンバー関数
    		void print();
    					// + 演算子のオーバーロード
    	friend Complex operator +(const Complex &, const Complex &);
    };
    
    void Complex::set(double a, double b)
    {
    	real      = a;
    	imaginary = b;
    }
    
    void Complex::print()
    {
    	printf("実数部 = %f, 虚数部 = %f\n", real, imaginary);
    }
    
    Complex operator +(const Complex &a, const Complex &b)
    {
    	Complex c;
    	c.real      = a.real + b.real;
    	c.imaginary = a.imaginary + b.imaginary;
    	return c;
    }
    
    int main()
    {
    	Complex x(1.0);   // 初期設定を行う場合
    	Complex y, z;   // 初期設定を行わない場合
    
    	x.print();
    	y.set(5.1, 1.5);
    	y.print();
    	z = x + y;
    	z.print();
    
    	return 0;
    }
    			

  8. ポインタとデストラクタ(「 C/C++ と Java 」の第12章参照)

      次に示す例における main 関数の 2 行目のように,new 演算子を使用してインスタンスに必要な領域を確保し,そのアドレスを返す方法を使用する場合は注意が必要です.new 演算子を使用しない場合は,「 x2 = x1 」の操作によって,x1 の内容がコピーされ,x2 に代入されます.つまり,右図の上(「 x2.real = 5.0 」を実行した後の状態)に示すように,x1 と x2 は全く別のものになり,x2 の値を変更しても x1 の値には全く影響ありません(逆も同様).

      しかし,new 演算子を使用した場合は,「 y2 = y1 」の操作によって,確保した領域のアドレスが y2 にコピーされ代入されるため,右図の下(「 y2->real = 5.0 」を実行した後の状態)に示すように,y1 と y2 は全く同じ場所を指すことになります.従って,y2 の指す場所の値を変更すると,y1 の指す場所の値も同様に変化します(逆も同様).

    #include <stdio.h>
    
    class Complex {
    	public:
    		double real;
    		double imaginary;
    					// 引数のないコンストラクタ
    		Complex() {};
    					// 引数のあるコンストラクタ
    		Complex(double a, double b = 0.0)
    		{
    			real      = a;
    			imaginary = b;
    		}
    };
    
    int main()
    {
    	Complex x1(1.0, 2.0), x2;
    	Complex *y1 = new Complex(3.0, 4.0), *y2;
    
    	x2      = x1;
    	x2.real = 5.0;
    	printf("x1 実数部 = %f, 虚数部 = %f\n", x1.real, x1.imaginary);
    	printf("x2 実数部 = %f, 虚数部 = %f\n", x2.real, x2.imaginary);
    	y2       = y1;
    	y2->real = 5.0;
    	printf("y1 実数部 = %f, 虚数部 = %f\n", y1->real, y1->imaginary);
    	printf("y2 実数部 = %f, 虚数部 = %f\n", y2->real, y2->imaginary);
    
    	return 0;
    }
    			

      たとえ,new演算子を使用しない方法でインスタンスを生成していても,クラス定義の中で new 演算子を使用している場合は同様のことが起こります.下に示す例においては,「 y = x 」の操作によって右図(「 y = x 」を実行した後の状態)のような状態になり,変数 s が同じ文字列を指すことになってしまいます.

      以下のプログラムの中に示すデストラクタ(勿論,一般的には,その中に記述してある printf 関数は必要ありません)とは,生成したインスタンスが必要なくなったとき,自動的に呼ばれ,後処理を行うための関数です.一般に,クラス定義の中で new 演算子を使用して領域を確保しているような場合は,その領域を開放する( delete 演算子の使用)ために必ず必要です.しかし,このプログラムの場合,変数 x ( y )で使用した領域を開放すると,y ( x )で使用していた領域もなくなってしまいます.従って,デストラクタの中に記述された delete 演算子の実行によってとんでもないことが起こる可能性があります.場合によっては,プログラムがハングアップしてしまいます.

    #include <stdio.h>
    #include <string.h>
    
    class String {
    	public:
    		int n;
    		char *s;
    					// 引数のないコンストラクタ
    		String () { n = 0; }
    					// 引数のあるコンストラクタ
    		String (char *a)
    		{
    			n = strlen(a);
    			s = new char [n+1];
    			strcpy(s, a);
    		}
    					// デストラクタ
    		~String()
    		{
    			if (n > 0) {
    				printf("	delete start %d\n", strlen(s));
    				delete [] s;
    				printf("	delete end\n");
    			}
    		}
    					// 指定した位置の文字を変更するためのメンバー関数
    		void mod(int k, char a)
    		{
    			s[k] = a;
    		}
    					// 出力するためのメンバー関数
    		void print()
    		{
    			printf("文字列 %s\n", s);
    		}
    };
    
    int main()
    {
    	String x("abcdefg"), y;
    
    	y = x;
    	y.mod(1, 'B');
    	x.print();
    	y.print();
    
    	return 0;
    }
    			

      上で述べた問題を解決したのが下に示すプログラム例です.代入演算子( = )をオーバーロードし,new 演算子で確保した領域も新たな領域にコピーしています(右図参照).
    #include <stdio.h>
    #include <string.h>
    
    class String {
    	public:
    		int n;
    		char *s;
    					// 引数のないコンストラクタ
    		String () { n = 0; }
    					// 引数のあるコンストラクタ
    		String (char *a)
    		{
    			n = strlen(a);
    			s = new char [n+1];
    			strcpy(s, a);
    		}
    					// デストラクタ
    		~String()
    		{
    			if (n > 0) {
    				printf("	delete start %d\n", strlen(s));
    				delete [] s;
    				printf("	delete end\n");
    			}
    		}
    					// 指定した位置の文字を変更するためのメンバー関数
    		void mod(int k, char a)
    		{
    			s[k] = a;
    		}
    					// 出力するためのメンバー関数
    		void print()
    		{
    			printf("文字列 %s\n", s);
    		}
    					// = 演算子のオーバーロード
    		String & operator= (const String &);
    };
    
    String & String::operator= (const String &b)
    {
    	if (&b == this)   // 自分自身への代入を防ぐ
    		return *this;
    	else {
    		if (n > 0)
    			delete [] s;    // 代入する前のメモリを解放
    		s = new char [b.n+1];   // メモリの確保
    		n = b.n;              // 値の代入
    		strcpy(s, b.s);
    		return *this;
    	}
    }
    
    int main()
    {
    	String x("abcdefg"), y;
    
    	y = x;
    	y.mod(1, 'B');
    	x.print();
    	y.print();
    
    	return 0;
    }
    			

      最後に,+ 演算子のオーバーロードによって,Java などのように,文字列の結合を + 演算子で行えるようにした例を示しておきます.
    #include <stdio.h>
    #include <string.h>
    
    class String {
    	public:
    		int n;
    		char *s;
    					// 引数のないコンストラクタ
    		String () { n = 0; }
    					// 引数のあるコンストラクタ
    		String (char *a)
    		{
    			n = strlen(a);
    			s = new char [n+1];
    			strcpy(s, a);
    		}
    					// デストラクタ
    		~String () {
    			if (n > 0) {
    				printf("	delete start %d\n", strlen(s));
    				delete [] s;
    				printf("	delete end\n");
    			}
    		}
    					// 出力するためのメンバー関数
    		void print()
    		{
    			printf("文字列 %s\n", s);
    		}
    					// = 演算子のオーバーロード
    		String & operator= (const String &);
    					// + 演算子のオーバーロード
    	friend String & operator +(const String &, const String &);
    };
    
    String & String::operator= (const String &b)
    {
    	if (&b == this)   // 自分自身への代入を防ぐ
    		return *this;
    	else {
    		if (n > 0) {
    			printf("	delete for =\n");
    			delete [] s;    // 代入する前のメモリを解放
    		}
    		s = new char [b.n+1];   // メモリの確保
    		n = b.n;              // 値の代入
    		strcpy(s, b.s);
    		return *this;
    	}
    }
    
    String & operator +(const String &a, const String &b)
    {
    	String *str = new String();
    
    	str->n = a.n + b.n;
    	str->s = new char [str->n+1];
    
    	strcpy(str->s, a.s);
    	strcat(str->s, b.s);
    
    	return *str;
    }
    
    int main()
    {
    	String x("abc"), y("defg"), z("ABCDE");
    
    	z = x + y;
    	z.print();
    
    	return 0;
    }
    			

  9. 継承(「 C/C++ と Java 」の第13章参照)

      クラスにおいて,継承は重要な機能です.Window のプログラムを考えてみてください.多くのアプリケーションにおいて Window を利用していますが,その基本的構成はほとんど同じです.もし,Window アプリケーションを作成するたびに,その全てに関するプログラムを書かなければならないとしたら大変な作業になります.基本的な Window の機能を持つクラスを定義しておき,個々の Window アプリケーションは,そのクラスを利用できるとしたら非常に便利です.これを実現するのが継承です.あるクラスの性質を継承するための一般的宣言方法は,
    class クラス名 : [アクセス権] 基底クラス名 [,[アクセス権] 基底クラス・・・] {
    	変更部分
    }
    			
    となります.このとき,元になったクラスを基底クラス(複数可),そのクラスを継承したクラスを派生クラスと呼びます.

      アクセス権を省略すると,基底クラスが struct ならば public,class ならば private とみなされます.private の場合は,基底クラスのすべてのメンバーは派生クラスのプライベート部に入れられますが(一部だけ,public に入れることも可能),public の場合は,基底クラスのパブリックメンバーはそのまま派生クラスのパブリックメンバーになります.また,基底クラスが複数の場合を多重継承と呼びます.

      継承を利用すれば,指定されたクラスの機能を受け継ぎ,新しいクラスに必要な機能の追加・修正だけを行えばよくなります.例えば,以下のプログラム(あまり良い例ではありませんが)においては,クラス Number は,クラス Base を継承しています.

      基底クラスにおいて,「 protected 」指定された変数や関数は,そのクラスを継承した派生クラスだけから参照可能です.また,この例の場合は,クラス Number の定義において,public 指定をしてクラス Base を継承していますので,クラス Base の「 public 」指定された部分は,クラス Number の「 public 」部分に入ります.そのため,3 つのメンバー関数をクラスの外から参照可能になります.

      また,コンストラクタやデストラクタは継承されません.そのため,基底クラスにパラメータを必要とするコンストラクタが存在する場合は,派生クラスから基底クラスのコンストラクタへパラメータを引き渡してやる必要があります(「 Number (int n1, int sp = 2) : Base(sp) 」における Base(sp) の部分).

    #include <stdio.h>
    
    class Base {
    	protected:
    		int n;
    		int step;
    					// コンストラクタ
    		Base (int sp)
    		{
    			step = sp;
    		}
    	public:
    					// 出力するためのメンバー関数
    		void print() { printf("value = %d\n", n); }
    					// 加算
    		void add() { n += step; }
    					// 減算
    		void sub() { n -= step; }
    };
    
    class Number : public Base   // Base の継承
    {
    	public:
    					// コンストラクタ
    		Number (int n1, int sp = 2) : Base(sp)
    		{
    			n = n1;
    		}
    };
    
    int main()
    {
    	Number x(10), y(5, 4);
    
    	x.add();
    	x.print();
    	y.sub();
    	y.print();
    
    	return 0;
    }
    			

  10. テンプレート(「 C/C++ と Java 」の第14章参照)

    1. 関数テンプレート

        例えば,2 つの値の大きさを比べ,小さい方を返す関数について考えてみます.今までの方法では,各引数の型毎に関数を定義しなければなりません(関数名のオーバーロード).しかし,次の形式で書かれる関数テンプレートを使用することによって,1 つの関数の定義だけですますことが可能です
      template <テンプレート引数宣言> 関数宣言または定義
      				
        テンプレート引数は関数宣言や定義の中で型名として使用でき,この型が任意の型に変換されます.例えば,次の例では,型 cl の部分に double や int が入ります.クラス型の変数に対しても次の関数を使用できますが,そのクラスのオブジェクトの大きさを比較する演算子「 < 」が定義されていなければ成りません.また,この例の場合,関数の 2 つの引数は同じ型 cl ですので,異なった型との比較はエラーになります.

      #include <iostream>
      
      template <class cl> cl min(cl a, cl b) {
      	return a < b ? a : b;
      }
      
      int main()
      {
      	int i = 0, j = 1, k;
      	double x = 10.0, y = 5.5, z;
      
      	k = min(i, j);
      	z = min(x, y);
      
      	std::cout << k << "  " << z << std::endl;
      
      	return 0;
      }
      				

    2. クラステンプレート

        クラスに対しても,関数と同様,以下のような形式でクラステンプレートを定義できます.
      template <テンプレート引数宣言> class name {
      	・・・・・
      };
      				
        ただし,コンストラクタ,デストラクタ,及び,メンバー関数の本体をクラス定義の外側に記述する場合は,関数テンプレートによって記述する必要があります.したがって,以下に示すような記述方法になります.
      			// コンストラクタ(デストラクタも同様)
      template <テンプレート引数宣言> クラス名 <テンプレート引数> :: クラス名 (引数)
      {
      	・・・・・
      };
      			// メンバー関数
      template <テンプレート引数宣言> クラス名 <テンプレート引数> :: メンバー関数名 (引数)
      {
      	・・・・・
      };
      				
        次の例では,クラステンプレートを使用して,任意のサイズ(この例では,3 )の様々な型の 1 次元配列を確保するクラスを定義しています.メンバー関数 input をクラス定義内で記述し,コンストラクタとメンバー関数 print はクラスの外で記述しています.

      #include <iostream>
      using namespace std;
      
      template <class Tp, int size> class Vector {
      		Tp *p;
      	public:
      		Vector();   // コンストラクタ
      		void print(int);   // 出力
      		void input()   // 入力
      		{
      			int i1;
      			for (i1 = 0; i1 < size; i1++) {
      				cout << "   " << i1+1 << " 番目の要素は? ";
      				cin >> p[i1];
      			}
      		}
      };
      
      template <class Tp, int size> Vector <Tp, size>::Vector()
      {
      	p  = new Tp [size];
      }
      
      template <class Tp, int size> void Vector <Tp, size>::print(int k)
      {
      	if (k < 0 || k > size-1)
      		cout << "   要素番号が不適当です\n";
      	else
      		cout << "   " << (k+2) << " 番目の要素は " << p[k] << endl;
      }
      
      int main()
      {
      	int k = 0, sw;
      
      	cout << "整数(0) or 実数(1) ? ";
      	cin >> sw;
      					// 整数
      	if (sw == 0) {
      		Vector < int, 3 > iv;
      		cout << "整数ベクトル\n";
      		iv.input();
      		while (k >= 0) {
      			cout << "要素番号は ";
      			cin >> k;
      			iv.print(k);
      		}
      	}
      					// 実数
      	else {
      		Vector < double, 3 > dv;
      		cout << "実数ベクトル\n";
      		dv.input();
      		while (k >= 0) {
      			cout << "要素番号は ";
      			cin >> k;
      			dv.print(k);
      		}
      	}
      
      	return 0;
      }
      				

静岡理工科大学 菅沼ホーム C/C++ と Java 目次