<C++編>


C言語の研究・C++編1

いままで一生懸命C言語の完全マスターを目指して頑張ってきた。しかし、C言語は奥が深く、まだ学んでいないことも一杯残されている。

一般には完璧にC言語をマスターしようと思えば数年以上かかると言われている。

しかし、我々にそんな時間はない。したがって、これからは、Visual C++などで実践プログラムを作成する途上で、知らないことが湧出される度に、その新しい知識について各自研究することとしてほしい。

このC++編では、いままで学んできたC言語の知識に加えて、C言語にないC++の利点や特徴について研究して行こう。

なお、ここからの部分は情報処理技術者試験の範囲外なので注意してほしい。

またここからは、純粋のC言語コンパイラではコンパイルできないので、ANSI C/C++コンパイラを用意してほしい。

はじめに

C言語が1972年にアメリカのAT&Tベル研究所において、Dennis M .Ritchieによって設計され、1989年にANSI(American National Standard Institute:米国国内規格協会)が標準規格を定めたことについては「はしがき」において述べた。

C++の誕生はANSI Cが規格された時より昔にさかのぼる。

1982年に同じくAT&Tベル研究所のBjane StroustrupによってCを+1、つまりC++したC言語より1つ進んだ言語として開発されたもので、C言語のスーパーセット言語といわれている。

平林雅英氏の「ANSI C/C++辞典」によると、C++はC言語を包含し、さらに以下の機能を追加したものとされている。

・クラス概念〜プログラムのカプセル化

・メンバ関数、フレンド関数〜情報隠蔽(information hiding)

・関数名、演算子のオーバーロード〜多重定義

・インライン関数〜マクロ展開関数の信頼性向上

・仮想関数〜オブジェクト指向関連

ここでは、これらのうちプログラム作成上、C言語に加えて実際に必要となる部分だけ学んで行くこととしよう。

第1章 クラスの概要

まず次の構造体を使った例題をみよう。

以前に見た覚えがあると思うが、2人分の学生のデータ、学生番号、名前、4科目の得点の各データを構造体として与え、各学生の4科目の平均点を求めて、学生番号、名前、平均点を表示するプログラムである。

#include <stdio.h>

typedef struct seiseki {   
    int bango;
    char *name;
    int ten[4];
    double ave;
} RESULT;

double get_ave(int *);
void print_out(RESULT *);

void main()
{
    RESULT a[2]={
        {1001,"AKIRA",97,95,98,99,0.0},
        {1002,"Pell",99,99,99,100,0.0}
    };
   
    RESULT *pa;
    pa = &a[0];
   
   
    printf("番号    名前     平均点\n");
   
    int i;
    for(i=0; i<2; i++){
        (pa+i)->ave = get_ave((pa+i)->ten);
        print_out(pa+i);
    }
}
   
double get_ave(int *t)
{
    int i;
    double ave;
    for(i=0; i < 4; i++) ave+= *(t+i);
    return ave/=4;
}

void print_out(RESULT *p)
{
    printf("%d    %s     %5.2f\n",p->bango, p->name, p->ave);
}

懐かしいだろうか。ここで赤字の位置に注目してほしい、後に説明するが、C言語では変数の宣言位置は関数の最初でなければならなかった。しかしこれがC++では許される。このことはもう今の時点でC++の世界に入っていることを意味する。

さて、このプログラムでは学生データの構造体を宣言し、そこにメンバとして、bango,name,ten[4],aveを指定した。これとは別に平均点を求める関数、学生番号と名前と平均点を表示する関数を別に定義した。

この章ではこれをC++のクラスを利用して書き直すことを一応の目的としておこう。

1 クラスの意義

C++におけるクラスは次のような形で宣言する。

class  クラス名 {

   public:

      コンストラクタ関数

      型指定子 メンバ名

       :

   メンバ関数

       :

   デストラクタ関数 

   private:

      型指定子 メンバ名

       :

   メンバ関数

       :

};

構造体はメンバとして型の異なるデータをレコードとして扱うことが出来たが、クラスはこのメンバに加えて関数も含めることができる。

この関数は当然、メンバを引数とした関数でメンバ関数あるいはメソッドと呼ばれている。

ということは、クラス自体が1つの立派なプログラムということになるわけである。

まず簡単な例として、四角形の縦と横の長さを与えて、四角形の面積を表示するプログラムを見ながら研究することにしよう。

#include<stdio.h>

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    ~CRectangle(); //デストラクタのプロトタイプ

    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
}

CRectangle::~CRectangle() //デストラクタ
{
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    MyRectangle.DispArea();
}

いかがだろうか。注釈方法をC++のものを採用した。//に続いて行末までを注釈と解される。

これを実行してもらって、結果を見ていただいた上で解説していこう。

(1) コンストラクタ関数

まず最初にクラスのpublic:というのに続いて、アンダーラインを引いておいたが、クラス名と全く同じ名前で、戻り値のない関数が出てきた。

これがコンストラクタ関数である。

コンストラクタ関数はクラス・オブジェクトを初期設定するメンバ関数で、オブジェクト作成時に自動的に呼び出される。引数はなくてもいいが、メンバの引数があればそれでそのメンバを初期化する。

この例では、オブジェクトMyRectangle作成時に(10,5)を初期値としてコンストラクタに与えている。

まず、クラス宣言部の中でコンストラクタ関数のプロトタイプを書いて、宣言部の外部でコンストラクタ関数を定義している。これは内部で定義しても別にいいがクラス宣言が長くなってしまうので、普通はクラスの外側で定義する。

このクラスの外側でコンストラクタ関数に限らず、メンバ関数を定義する場合で忘れてはならないことは、クラスがいくつも宣言されている場合などは、どのクラスに属するメンバ関数なのかをはっきりさせておくために、

:: というコロンを2つ並べたスコープ解決演算子と呼ばれる演算子を使用して、

戻り値 クラス名::メンバ関数名(引数)

    関数の定義 

と書く。

ただ、コンストラクタ関数については、このようなオブジェクト作成時に自動的に呼び出されるという性質から、戻り値はvoidであるが、絶対にvoidと書くのはやめておかなければならない

コンストラクタ関数はコンピュータのメモリ空間にこのクラス・オブジェクトの領域を確保する働きがある。

(2) デストラクタ関数

光があれば闇があるように、オブジェクト作成時に呼び出されるコンストラクタ関数があれば、オブジェクトが削除される度に自動的に呼び出されるデストラクタ関数がある。

デストラクタ関数には、戻り値も引数も指定してはならない、全てvoidであるが、これらを書いてはならない

クラス名の頭にチルダ~を付けて表す。

デストラクタ関数はコンピュータのメモリ空間からこのクラス・オブジェクトの領域を削除する働きがある。

デストラクトしないとメモリ空間が次第に少なくなってしまう。

それではここで、先ほどのプログラムにコンストラクタ関数とデストラクタ関数の部分にprintf関数を挿入してその自動呼出しの様子を見てみよう。

#include<stdio.h>

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    ~CRectangle(); //デストラクタのプロトタイプ

    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    MyRectangle.DispArea();
}

どうだろうか、赤字でprintf関数をそれぞれ追加したが、コンストラクタやデストラクタが自動的に実行されている様子がよくお分かりいただけたと思う。

(3) メンバ関数のメリット

このプログラムのDispAreaというメンバ関数に注目していただきたい。

普通、関数を使用して表示なり計算などをさせる場合は引数を与えてやることが必要である。そうでないと関数は一体何をデータとして処理していいのか分からないだろう。

しかし、このメンバ関数DispAreaには引数はない。なのにちゃんと縦と横を掛け算して面積を計算している。

このように、メンバ関数は同じクラスのメンバであれば引数としてわざわざ与えなくても、それらを自由に使うことが出来る。

これがメンバ関数のメリットと言えよう。

(4) プライベートとハブリック

先のプログラム例は全てpublicとして定義した。

定義方法は、public : とpublicの後にコロンを付ける。

publicとは「公開する」というような意味で、たとえばメンバのm_Widthにアクセスしようと思えば

MyRectangle.m_Width = 100;

というようにドット演算子を使用してアクセスできる。

ここで、m_Widthやm_Heightを「公開したくない」というような場合が考えられる。

アクセスできるということは、その値をポインタを使用すれば簡単に書き換えられてしまうからだ。

ちょっとやってみよう。

#include<stdio.h>

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int ); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    ~CRectangle(); //デストラクタのプロトタイプ

    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    CRectangle *p = &MyRectangle; //ボインタの設定
    p->m_Width = 100; //横の値の書き換え
    p->m_Height = 100; //縦の値の書き換え
    MyRectangle.DispArea();
}

アメリカ人なら「オーマイガー」と言われるかもしれない。

10×5の長方形のつもりが、天才ハッカーによって100×100の正方形に書き換えられてしまったからだ。

このように他からのアクセスを防ぐために「非公開」にする方法がある。

それがプライベートで、定義方法は private : とprivateの後にコロンを付ける。

そりでは今のm_Widthとm_Heightを非公開、privateにしてみよう。

#include<stdio.h>

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int ); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    ~CRectangle(); //デストラクタのプロトタイプ

private:
    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    CRectangle *p = &MyRectangle; //ボインタの設定
    p->m_Width = 100; //横の値の書き換え〜エラー
    p->m_Height = 100; //縦の値の書き換え〜エラー
    MyRectangle.DispArea();
}

どうだろうか。これを実行するとよほどアホのコンパイラではない限り、赤字の部分で「アクセスできません」というエラーメッセージを出すことだろう。

これが情報隠蔽(information hiding)というものである。

private、publicのどちらも指定されていないメンバやメンバ関数については、この情報隠蔽を重視する観点から、デフォルトでprivateとなるから注意しよう。

それでは、このプライベートなメンバにアクセスするにはどうすればいいのだろう。

それが先ほど説明したメンバ関数の役割である。メンバ関数はそのクラスのメンバに自由にアクセスすることが出来る。

ユーザはこのメンバ関数を経由してプライベートなメンバにアクセスすることが許される訳である。この意味で、そういうメンバ関数をインターフェイス関数とも呼ばれている。

そこで、インターフェイス関数・ChangeValueを作成してプライベートメンバにアクセスしてみよう。

#include<stdio.h>

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    void ChangeValue(int, int);
    ~CRectangle(); //デストラクタのプロトタイプ

private:
    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void CRectangle::ChangeValue(int a, int b)
{
    m_Width = a;
    m_Height = b;
}


void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    CRectangle *p = &MyRectangle; //ボインタの設定
    p->ChangeValue(100,100); //インターフェイス関数にアクセス
    p->DispArea();
}

どうだろうか、インターフェイス関数・ChangeValueのおかげで長方形を正方形に変えられただろうか。

(5) ヘッダーファイルへの移動

大体クラスの概要は分かってもらえただろうか。

それでは、C++プログラムらしくクラス定義の部分をカットしてヘッダーファイル(.hが付くファイル)を新規作成して、これに貼り付けよう。

方法は以下のとおりである。

まず、プロジェクトにヘッダーファィルを新規作成し、これに先ほどのプログラムの以下の部分、

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    void ChangeValue(int, int);
    ~CRectangle(); //デストラクタのプロトタイプ

private:
    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void CRectangle::ChangeValue(int a, int b)
{
    m_Width = a;
    m_Height = b;
}

これをプログラムファイルから切りとって、ヘッダーファイルに貼り付けよう。

そしてこのヘッダーファイルをたとえば「cpp1.h」のように名前を付けてプロジェクトのあるディレクトリに保存しておこう。

するとプログラムファイルの方は次のように簡潔になっていることだろう。

#include<stdio.h>

void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    CRectangle *p = &MyRectangle; //ボインタの設定
    p->ChangeValue(100,100); //インターフェイス関数にアクセス
    p->DispArea();
}

このプログラムに次の赤字の部分つまりヘッダーファイルのインクルードを追加しておこう。

#include<stdio.h>
#include "cpp1.h"

void main()
{
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    CRectangle *p = &MyRectangle; //ボインタの設定
    p->ChangeValue(100,100); //インターフェイス関数にアクセス
    p->DispArea();
}

さあ、これでコンパイルして実行してみよう。

どうだろうか、うまく実行できただろうか。これがC++流プログラム体系だ。

2 関数のオーバーロード

C言語にはないが、C++にある便利な機能として関数のオーバーロードを上げることができる。

関数のオーバーロードとは複数の関数に同じ名前をつけることができることをいう。

さっそく見て行こう。

先ほどのサンプルプログラムでインターフェイス関数・GetValueを作った。これにより四角形の縦、横両方の値を初期値から変化させた。

しかし、縦か横かどちらかだけの値を変化させたい場合もあるだろう。

そこで、引数の与え方をちょっと変えて、整数値と文字定数を引数として与え、文字定数がアルファベットの'W'か'w'のときは横の値を、'H'か'h'のときは縦の値をそれぞれ変化させる関数をやはりChageValueと名付けてオーバーロードさせてみよう。

まず、ヘッダーファイルの内容を以下のようにちょっと変えておこう。

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int , int); //コンストラクタのプロトタイプ
    void DispArea(); //メンバ関数のプロトタイプ
    void ChangeValue(int, int);
    void ChangeValue(int, char); //関数のオーバーロード
    ~CRectangle(); //デストラクタのプロトタイプ

private:
    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void CRectangle::ChangeValue(int a, int b)
{
    m_Width = a;
    m_Height = b;
}

void CRectangle::ChangeValue(int a, char c)
{
    if(c == 'w' || c == 'W')     m_Width = a;
    else if(c == 'h' || c == 'H') m_Height = a;
}

赤字の部分が今回オーバーロードさせた関数・ChangeValueである。

さらにプログラムファイルの方も、ユーザに整数値と縦横の区別をキーボードから入力してもらうために例のscanf関数を使える形に直しておこう。

#include<stdio.h>
#include "cpp1.h"

void main()
{
    int i;
    char c;

    printf("整数と縦横の区別(縦はHかh,横はWかw)をスペースで区切って入れてください\n");
    scanf("%d %c",&i ,&c);
   
    CRectangle MyRectangle (10, 5); //このクラスのオブジェクトMyRectangleの宣言
    CRectangle *p = &MyRectangle; //ボインタの設定
    printf("-----ChangeValue1-----\n");
    p->ChangeValue(100, 100); //インターフェイス関数にアクセス
    p->DispArea();
   
    printf("-----ChangeValue2-----\n");
    p->ChangeValue(i, c); //オーバーロードされたインターフェイス関数にアクセス
    p->DispArea();
}

さあ用意は出来た。これでコンパイルして実行してみよう。

最初のChangeValue関数で縦と横がそれぞれ100に設定され、次のオーバーロードされたChangeValue関数で縦または横が指定された値に変化したことが確認できただろうか。

3 関数における変数の宣言位置とデフォルトパラメータ

C言語にはないが、C++にある便利な機能としてもう一つ、変数の宣言の位置を上げることができる。

たとえば、基本編で前置増分と後置増分のテスト例として使用した次のプログラムを見てみよう。

#include <stdio.h>

void main(int argc, char **argv)
{
    int i = 10;
    int x1;

    x1 = 10 * i++;
    printf("x1 = %d i = %d \n",x1 ,i);

    i = 10;

    int x2; //変数の宣言位置
    x2 = 10 * ++i;
    printf("x2 = %.*d i = %d \n",2,x2 ,i);
}

このプログラムを純粋のC言語専用コンパイラでコンパイルするとエラーとなるはずである。というのは、C言語では変数の宣言位置は関数の先頭でなければならないからである。

ところが、C++では変数を使用する前ならその宣言する位置はどこでもいい

したがって、このプログラムもVisual C++であればなんら支障なく実行可能である。

最初の構造体を使ったプログラムもこの原理を利用したわけである。

さらにC言語には存在しないC++の便利な機能のもう1つは、関数のデフォルトパラメータである。

C++では関数のプロトタイプでデフォルトパラメータを与えておくことができる。

前の四角形のプログラム例で、今回はオブジェクト作成時に初期値を設定せず、そのかわりコンストラクタ関数のプロトタイプ宣言の部分でデフォルトパラメータを指定しておこう。

まず、プログラムファイルの方を次のように変更しておこう。

#include<stdio.h>
#include "cpp1.h"

void main()
{
    int i;
    char c;

    printf("整数と縦横の区別(縦はHかh,横はWかw)をスペースで区切って入れてください\n");
    scanf("%d %c",&i ,&c);
   
    CRectangle MyRectangle; //このクラスのオブジェクトMyRectangleの宣言(初期値は未設定)
    CRectangle *p = &MyRectangle; //ボインタの設定
    printf("-----Default-----\n");
    //p->ChangeValue(100, 100); //インターフェイス関数にアクセス
    p->DispArea();
   
    printf("-----ChangeValue2-----\n");
    p->ChangeValue(i, c); //インターフェイス関数にアクセス
    p->DispArea();
}

このように、オブジェクトMyRectangle作成時には初期値設定はなしとして、最初のChangeValue関数は注釈文に変えてしまって、デフォルト値が表示されるように直しておこう。

デフォルト値は次のようにして、ヘッダーファイルでコンストラクタ関数のプロトタイプでデフォルト値を指定しておこう。

class CRectangle //CRectangleクラスの宣言
{
public:
    CRectangle(int w = 10, int h = 10); //コンストラクタのプロトタイプにデフォルトパラメータを入れる
    void DispArea(); //メンバ関数のプロトタイプ
    void ChangeValue(int, int);
    void ChangeValue(int, char); //関数のオーバーロード
    ~CRectangle(); //デストラクタのプロトタイプ

private:
    int m_Width; //メンバ〜横の長さ
    int m_Height; //メンバ〜縦の長さ
};

CRectangle::CRectangle(int w, int h) //コンストラクタ
{
    m_Width = w;
    m_Height = h;
    printf("オブジェクトを作成しました\n");
}

CRectangle::~CRectangle() //デストラクタ
{
    printf("オブジェクトを削除しました\n");
}

void CRectangle::DispArea()
{
    int iArea;

    iArea = m_Width * m_Height;

    printf("縦%d、横%dの四角形の面積は %d です\n",m_Height, m_Width ,iArea);
}

void CRectangle::ChangeValue(int a, int b)
{
    m_Width = a;
    m_Height = b;
}

void CRectangle::ChangeValue(int a, char c)
{
    if(c == 'w' || c == 'W')     m_Width = a;
    else if(c == 'h' || c == 'H') m_Height = a;
}

このようにヘッダーファイルの赤字のプロトタイプ部分でデフォルトパラメータを指定した。

さあ、これで実行してみよう。

どうだろうか、初期値としてコンストラクタ関数のプロトタイプ部分で指定したデフォルトパラメータが縦、横の値として使用されている様子がお分かりいただけただろうか。

4 構造体からクラスへ

それでは最初の例題、「2人分の学生のデータ、学生番号、名前、4科目の得点の各データを構造体として与え、各学生の4科目の平均点を求めて、学生番号、名前、平均点を表示するプログラム」に戻ろう。

もう一度最初の構造体を使用したプログラムを見返してみよう。

#include <stdio.h>

typedef struct seiseki {   
    int bango;
    char *name;
    int ten[4];
    double ave;
} RESULT;

double get_ave(int *);
void print_out(RESULT *);

void main()
{
    RESULT a[2]={
        {1001,"AKIRA",97,95,98,99,0.0},
        {1002,"Pell",99,99,99,100,0.0}
    };
   
    RESULT *pa;
    pa = &a[0];
   
   
    printf("番号    名前     平均点\n");
   
    int i;
    for(i=0; i<2; i++){
        (pa+i)->ave = get_ave((pa+i)->ten);
        print_out(pa+i);
    }
}
   
double get_ave(int *t)
{
    int i;
    double ave;
    for(i=0; i < 4; i++) ave+= *(t+i);
    return ave/=4;
}

void print_out(RESULT *p)
{
    printf("%d    %s     %5.2f\n",p->bango, p->name, p->ave);
}

さて、これをクラスに変えるわけだが、クラスのメンバとしてコンストラクタ関数とデストラクタ関数は当然含めるとして、まずメンバから考えて行こう。

メンバについては構造体のプログラムと同様、学生番号、名前、4科目の得点、平均点

    int bango;
    char *name;
    int ten[4];
    double ave;

を当然含めればいいだろう。そしてこれは例によって非公開のprivateにすればよいだろう。

それではメンバ関数として何を加えるかについてだが、これもこの構造体のプログラムと同様に平均点を求める関数と表示する関数、つまりこの構造体プログラムでいうところの、

double get_ave(int *);
void print_out(RESULT *);

以上の2つの関数であろう。

この2つの関数をメンバ関数に加えるということは、メンバ関数は引数としてそのクラスメンバの値などを与えてやらなくても、自分で見ることができるから便利である。だからこの2つの関数をメンバ関数に指定した場合は次のような形になるだろう。

double get_ave();
void print_out();

ご覧のとおり引数なしだ。

それではここまでの考えから、先にクラス宣言部分を次に作ってみよう。

class seiseki {
    public:
        seiseki(int, char *, int [],double ave=0.0); //コンストラクタ、平均点にデフォルト値を設定
        double get_ave();
        void print_out();
        ~seiseki(); //デストラクタ
    private:
        int bango;
        char name[10];
        int ten[4];
        double ave;
};

こういう感じでいいだろう。ここまでは意外と簡単に事が進んだ。

しかし、難しいのはここからである。コンストラクタ関数の赤字の部分を見ていただきたい。4科目の得点の配列を指している。

この配列にデータを入れるためにはどのようにすれば良いのだろうか。

それにはブルース・リー氏が教えてくれたポインタを使用すればいい。つまり2人分の学生の得点配列だけを別に、次のように用意して、それぞれを指すポインタを設定しよう。

    int ten[2][4] ={
        {97,95,98,99},
        {99,100,100,100}
    };

    int *p1= ten[0];
    int *p2 = ten[1];

こいつ、つまり配列を渡す代わりに、それぞれのオブジェクト作成時にp1、p2と配列へのポインタを与えてやればなんとかなりそうである。

ついでに、文字列への値の代入はイコール文は使えないので、strcpy関数を使用しよう。

メンバ関数については構造体と同じ方法でOKそうである。

それでは、クラス宣言の配列のところをポインタに変えて、この構造体プログラムをクラスプログラムに変えてみよう。

それは、次のようになるだろう。

#include <stdio.h>
#include <string.h>

class seiseki {
    public:
        seiseki(int, char *, int *,double ave=0.0); //コンストラクタ、平均点にデフォルト値を設定
        double get_ave();
        void print_out();
        ~seiseki(); //デストラクタ
    private:
        int bango;
        char name[10];
        int ten[4];
        double ave;
};

seiseki::seiseki(int bango, char *name, int *p, double ave)
{
    int i;
    seiseki::bango = bango;
    strcpy(seiseki::name, name);
    for(i=0; i < 4; i++) seiseki::ten[i] = *(p+i);
    seiseki::ave = get_ave();
    printf("seisekiクラス%sのコンストラクタ\n",name); //確認用
}

seiseki::~seiseki()
{
    printf("seisekiクラス%sのデストラクタ\n",name); //確認用
}

double seiseki::get_ave()
{
    int i;
    int ave = 0;
    for(i=0; i < 4; i++)    ave += *(ten +i);
    return (double)ave/4.0;
}

void seiseki::print_out(void)
{
    printf("%d    %s     %5.2f\n",bango, name, ave);
}


void main()
{
    int ten[2][4] ={
        {97,95,98,99},
        {99,100,100,100}
    };

    int *p1= ten[0];
    int *p2 = ten[1];

    seiseki akira (1001,"AKIRA", p1); //オブジェクトakiraの作成
    seiseki pell (1002,"Pell",p2 ); //オブジェクトpellの作成

    printf("番号    名前    得点\n");
    akira.print_out();
    pell.print_out();
}

どうだろうか。得点が配列だったためちょっとややこしかったかも知れない。

C++はC言語以上に型に厳格なので、構造体プログラムで構造体の値を初期化したような、

    seiseki a[2]={
        {1001,"AKIRA",97,95,98,99,0.0},
        {1002,"Pell",99,99,99,100,0.0}
    };

こういうイージーな書き方はできないので注意しよう。

しかし、構造体でよく使ったtypedef指定子はクラスでも使用できる。しかし新しい名前に付けかえるだけの働きしかなく、全く意味がないことと言ってもいいだろう。

さあ、またこのプログラムを次の章のために、ヘッダーファイルとプログラムファイルに分割しておこう。

すなわち、プログラムファイルから次の部分を切り取って、プロジェクトのあるディレクトリに拡張子に.hを付けた適当な名前のヘッダーファイルとして保存しておこう。(例:getave4.h)

class seiseki {
    public:
        seiseki(int, char *, int *,double ave=0.0); //コンストラクタ、平均点にデフォルト値を設定
        double get_ave();
        void print_out();
        ~seiseki(); //デストラクタ
    private:
        int bango;
        char name[10];
        int ten[4];
        double ave;
};

seiseki::seiseki(int bango, char *name, int *p, double ave)
{
    int i;
    seiseki::bango = bango;
    strcpy(seiseki::name, name);
    for(i=0; i < 4; i++) seiseki::ten[i] = *(p+i);
    seiseki::ave = get_ave();
    printf("seisekiクラス%sのコンストラクタ\n",name); //確認用
}

seiseki::~seiseki()
{
    printf("seisekiクラス%sのデストラクタ\n",name); //確認用
}

double seiseki::get_ave()
{
    int i;
    int ave = 0;
    for(i=0; i < 4; i++)    ave += *(ten +i);
    return (double)ave/4.0;
}

void seiseki::print_out(void)
{
    printf("%d    %s     %5.2f\n",bango, name, ave);
}

そして切り取られたプログラムファイルの頭の部分にinclude文を次のように追記しておこう。

#include <stdio.h>
#include <string.h>
#include "getave4.h"

void main()
{
    int ten[2][4] ={
        {97,95,98,99},
        {99,100,100,100}
    };

    int *p1= ten[0];
    int *p2 = ten[1];

    seiseki akira (1001,"AKIRA", p1); //オブジェクトakiraの作成
    seiseki pell (1002,"Pell",p2 ); //オブジェクトpellの作成

    printf("番号    名前    得点\n");
    akira.print_out();
    pell.print_out();
}

赤字の部分を追記しておいた。

どうだろうか、これで大体C++のクラスというものの感触をつかんでいただけただろうか。

back  next


HOME