このエントリーをはてなブックマークに追加
スポンサーリンク

C++入門

はじめに

C++はC言語を拡張したものです。つまり、基本的にはCと同じで、特にC流の記述のし方も許可されています。しかし、せっかくC++を使うのであれば、C++独自の記述をした方が、簡素でわかりやすい記述ができるようになります。

C++ではまず普通にC言語の記述をします。なので、最初にC言語入門でC言語を理解してください。その上で、C++ならではの記述をします。この章ではC言語はわかっているものとして、C++特有の書き方、ストリーム入出力マニピュレータ参照渡しクラスついて詳しく学習してゆきます。

ストリーム入出力

ストリーム入出力は、C言語のprintfやscanfに相当するもので、stdio.hのかわりに、iostream.hをヘッダーに組み込んで使用します。

出力
出力は、C言語のprintfのようなもので、画面に文字を出力します。

例)
cout << "hello word\n":

ここで、<<は元来はシフト演算子なのですが、iostream.hの中で『データーを送る』という意味を追加(オーバーロード)されています。

入力
入力は、cinを使います。

例)
char keyin[80];
cout << "文字を入れてください";
cin >> keyin;

この事からもわかるように、<<は『coutへデーターを出力する』で、>>は『keyin(配列)にデーターを入れる』という意味になります。

フィールド幅指定
入出力フィールドの幅を指定する事ができます。

例)
cout.width(5);
cout << "10";

とすると、結果は、右詰めで『□□□10』と表示されます。ただし、この指定は1回だけ有効なので注意してください。次にcoutが来ると解除されてしまうので、widthを指定する時は、必ずcout毎に指定してください。

また、小数点以下の値を指定するときは、precision指定を使います。

例)
precision(2);
cout << "10";

とすると、結果は『10.00』になります。

書式指定
coutで出力される書式は、フラグでセットする事もできます。フラグでセットすると、先の例のような1回限りではなく、unsetで解除しない限り有効になります。
フラグ 意味 フィールド
dec 10進数 basefield
oct 8進数
hex 16進数
showbase ベースを出力する
scentific サイエンティフィック表示にする floatfield
fixed 固定小数点表示にする
left 左詰めにする adjustfield
right 右詰めにする
internal 符号やベース記号の後に空白を入れる
例)
cout.setf(ios::hex);
16進数で表示させる

また、フィールドを指定することで、unsetを省く事ができます。たとえば、2番目のパラメーターにベースフィールドを指定すると、『ベースフィールドを、それまでの状態にかかわらず、1番目のパラメーターにする』という意味になります。

例)
cout.setf(ios::dec, ios::basefield):
ベースフィールドを10進数にする。

この場合、widthとは違い、unsetで解除するか、フィールド指定で別のフラグをセットするまで有効です。

マニピュレータ

書式指定を1個のcout文で続けて行う事ができます。ヘッダーファイルに、#include <iomanip.h>を指定します。
指定 意味
oct 8進数
dec 10進数
hex 16進数
ws 空白読み飛ばし
flush バッファの出力
endl バッファの出力+改行
ends バッファの出力+ヌル
setbase( ) ベースフィールドをセットする。(8、10、16、または0)
setiosflags( ) 書式フラグをセットする
resetiosflags( ) 書式フラグを解除する
setw( ) widthを指定する
setprecision( ) 小数点の桁数をセットする
setfill( ) 空白の部分に埋める文字をセットする
例)
cout << resetiosflags(ios::basefield) << setiosflags(ios::hex) << 255 << endl;
255を16進数に変換して、『ff』と、改行を出力する。

マニピュレーターを使う場合には、basefieldを一括指定できないので、直前にresetiosfieldを使ってセットしたいフラグをクリアーしてから、setiosfieldをセットしてください。

ファイル

ファイル操作には、C流の書き方である、open、close、fprintfの他に、fstream.hを使う事ができます。fstream.hには、iostream.hが含まれているので、ヘッダーとして『#include <fstream.h>を指定するだけで、ストリーム入出力をすべて使用することができます。

fstramを使えば、シーケンシャルファイルの扱いを簡単で記述する事ができます。(seekgやseekpを使えばランダムアクセス関数を作る事もできます)。

オープン
例)

ifstream fin;
fin.open("ファイル名",モード,属性);
ファイル名:
ファイル名(パス)を指定します。

モード:
in 入力。
binary バイナリモードでオープンする。
out 出力。
ate ファイルの終わりにポインターを移動させる。
app 追加書き込み。
trunc atrもappも指定されていない場合、既存のファイルを捨てる。
nocreate ファイルが存在しない場合オープンしない。(クリエートしない)
noreplace outモードの場合、ateかappがセットされていない場合はオープンしない。(リプレースしない)
モードを複数指定する場合は、 |で区切って指定します。

例)
ios::in | ios::nocreate
ファイルが存在しない場合はエラー。

属性:
共有モードを指定します。省略すると、コンパチブルになります。通常は省略します。

ファイル操作の失敗
ファイル操作(オープン、クローズ、リード、ライト)が失敗した場合、
if (!fin)
が真になります。

例)
if (!fin)
{
cerr << ""ファイルが見つかりません":
exit (-1)
}

また、fin.fail()としても『エラーなら』という意味になります。
fin.fail() エラーなら
!fin.fain() エラーじゃなければ
!fin エラーなら
fin エラーじゃなければ
ファイルの入出力
ファイルの入力には、get( )を使います。

int c;
get(c);
とすると、ファイルから1バイト入力してCに入れます。また、
fout << c
とすると、cをfoutに出力します。(foutは、出力用にオープンしておきます。)

また、getを使わずに、
fin >> c;
とするとわかりやすいのですが、この場合空白が読み飛ばされます。

他に以下のメンバー関数を使うことができます。
get 文字を取り出す。(デリミタは残す)
getline 文字を取り出す。(デリミタは破棄する)
read データーを取り出す。
ignore 文字を取り出して、破棄する。
peek 文字を返値に返す。ポインターは更新しない。
gcount 直前に取り出した文字数を返す。
eatwhite 空白文字を取り出す。
putback 文字を戻す。
sync 外部から入力される文字との同期を取る。
seekg 入力ポインタの変更。
tellg 入力ポインターを返す。
ws 空白文字を取り除く。
put 1バイト出力する。
write バイト列を出力する。
flush フラッシュする。
seekp 書き込みポインターを移動する。
tellp 書き込みポインタを返す。
ファイルの終わり
読み込みファイルがファイルが終わりになると、fin.eof( )が真になります。つまり、

while ( !fin.eof( )) {
}
とすると、『ファイルが終わるまでループ』という意味になります。

eofの他にも以下のメンバー関数を使用することができます。
good 正常なら真
bad 重大エラーなら真
fail 軽いエラーなら真
eof 終了なら真
clear エラーフラグをクリアー
rdstate ステータスを返す

00:エラーなし
01:EOF
02:Fail
04:BAD
クローズ
ファイルをクローズします。

例)
fin.close();

参照渡し

Cでポインター渡しをする場合、呼び出し側の変数の前に&をつけて、『この変数のアドレス』という引数にして、さらに呼ばれる側は、引数をポインターとして受け取るように記述する必要がありました。

例)
void prtstat(insigned char *);...呼ばれ側
prtstat(&pstpara.stat);.......呼び側

そこで、C++では呼ばれ側の引数に&をつける事で、予備側はポインター渡しか値渡しかを意識する事なく、ポインターを渡す事ができます。

例)
void prtstat(insigned char& stat);...呼ばれ側
prtstat(pstpara.stat);.......呼び側

さらに、ポインター渡しとは違い、引数を保護する事ができます。

例)
void prtstat(const insigned char& stat);...constをつける事で、参照専用である事を明記する。

参照渡しが、ポインター渡しと最も違う所は、即値を記述する事ができるという点です。たとえば、
prtstat(144);

とすると、実際にはメモリ上に144という数値が確保された後、その確保したアドレスが関数に渡ります。このように、参照渡しとは、ポインター渡しと値渡しの良いところを両方持つ渡し方だという事です。

クラス

「クラス」とは、C言語でいうところの構造体に似ています。しかし、クラスと構造体の違うところは、「変数だけでなく、関数もクラスのメンバーになる」という所です。この関数を「メンバー関数」と呼び、クラス内の変数を「データーメンバー」と呼びます。

データーメンバーはpublicで公開にするか、friendでフレンド関数にする以外はアクセスができなくなります。したがって、データーメンバーに値をセットする関数というのが必要になってきます。

このようにするのは、データーの安全性を高めるためであり、メンバー関数内で不適切な引数がセットされないように、初期化の段階で適切な処理をしたり、メンバー関数の値を別の関数内で変更されないようにする事を目的としています。
例)

#include <iostream.h>
#include <string.h>

class tokmas {
  private:
  char tk1[40];
  char tk2[10];
  char tk3_1[30];
  char tk3_2[30];
  char tk3_3[30];
  public:
  void settk1(char* TK1){strcpy(tk1,TK1);}
  void settk2(char* TK2){strcpy(tk2,TK2);}
  void settk3_1(char* TK3_1){strcpy(tk3_1,TK3_1);}
  void settk3_2(char* TK3_2){strcpy(tk3_2,TK3_2);}
  void settk3_3(char* TK3_3){strcpy(tk3_3,TK3_3);}
  void tokhyoji();
};

void tokmas::tokhyoji() {
  cout << "得意先名[" << tk1 << "]\n";
  cout << "住所 〒[" << tk2 << "]\n";
  cout << "    [" << tk3_1 << "]\n";
  cout << "    [" << tk3_2 << "]\n";
  cout << "    [" << tk3_3 << "]\n";
}

void main(){
  tokmas ssoft;
  ssoft.settk1("会社名");
  ssoft.settk2("郵便番号");
  ssoft.settk3_1("住所1");
  ssoft.settk3_2("住所2");
  ssoft.settk3_3(" ");
  ssoft.tokhyoji();
}

まず、この例をみて解説していきましょう。

class tokmas {
tokmasという、得意先マスターのクラスを定義しています。

private:
ここから先の変数(正式にはメンバーデータ)は、プライベートだという意味です。プライベートなメンバーデータは、そのクラス内でしかアクセスできないという意味になります。ちなみに、先頭のprivateは省略できます。(省略すると自動的にprivateになる)。ただし、ここでは説明用のためにあえて省略していません。

public:
ここから先のデーターメンバーおよびメンバー関数は公共だという意味です。つまり、外部からアクセスできますよ、という意味です。プライベートなデーターメンバーにアクセスするためには、メンバー関数を媒介にして値をセットします。

ここで、慣習的な事ですが、プライベートなメンバーに値をセットする関数を「set...」という名前にして、一時的に値をセットするための公共な変数を、大文字(あるいは、頭文字を大文字)にする事が多いようです。

ここで、private、protected、publicは以下のような意味を持ちます。
private そのクラス内のみアクセス可能
protected そのクラス内及び派生クラス内からアクセス可能
public そのクラス外からでもアクセス可能
void tokmas::tokhyoji() {
単に値をコピーするだけ、のような簡単な作業は一行で書けるため、メンバー関数の定義と同時に中身も書いています。これは、定義につづけて{ }で中身を書くだけです。しかし、中身が長くなった場合は、別の場所に書いておく方が便利です。この場合、クラスの定義の外で定義します。

外で定義するためには};でクラスを閉じた後、(セミコロンに注意!)、void tokmas::tokhyoji( ) { という風に定義します。ここで、tokmas::は「tokmasの」と読みます。したがって、tokmas::tokhyojiは、「tokmasのtokhyojiを定義する」という意味です。ここでは、定義した得意先マスターを表示しています。

tokmas ssoft;
もし、これがC言語で構造体ならstaticと書くべき所なのでしょうが、ここではクラスなのでstaticとは書きません。(そもそもC++では構造体であっても、static記述は不要です)。ここでは、ssoftというクラスの実体を宣言しています。

ssoft.settk1("Aufheben Software");
settk1という公共のメンバー関数を呼んで、得意先名をセットします。具体的には、tokmasクラスのプライベート関数に、strcpyを使って値をセットするだけです。ここでは単に値をセットするだけなのですが、たとえば、機種依存文字や半角文字が含まれていたら変換するとか、空白なら別の文字(未定義とか)に置き換えるとか、そういった処理を行う事もでき、関数の安全性を高めています。

ssoft.tokhyoji();
ssoftの実体を表示する関数を呼びます。tokhyoji()が公共なのでmain()からでも呼び出すことができます。

コンストラクタ・デストラクタ

クラスの実体を宣言した時に、最初に呼ばれる初期化ルーチンが「コンストラクタ」です。

先の例では、メンバーデータの確保を配列で行っていましたが、これをポインターで宣言した場合の事を考えます。この場合、ポインターだけで実体がないので、別の場所で実体を確保しなければなりません。この実体の確保は、クラスの実体の宣言と同時に行われると便利です。そこで、コンストラクタを使います。

コンストラクタを使う為には、クラスと同一名のメンバー関数が必要です。
例)

#include <iostream.h>
#include <string.h>

class tokmas {
  private:
  char* tk1;
  char* tk2;
  char* tk3_1;
  char* tk3_2;
  char* tk3_3;
  public:
  tokmas(char* TK1, char* TK2, char* TK3_1, char* TK3_2, char* TK3_3);
  ~tokmas();
  void tokhyoji();
};

//tokmasコンストラクタ
tokmas::tokmas(char* TK1, char* TK2, char* TK3_1, char* TK3_2, char* TK3_3) {
  tk1 = new char[40];
  tk2 = new char[10];
  tk3_1 = new char[30];
  tk3_2 = new char[30];
  tk3_3 = new char[30];

  strcpy(tk1,TK1);
  strcpy(tk2,TK2);
  strcpy(tk3_1,TK3_1);
  strcpy(tk3_2,TK3_2);
  strcpy(tk3_3,TK3_3);
}

//tokmasデストラクタ
tokmas::~tokmas() {
  delete [] tk1;
  delete [] tk2;
  delete [] tk3_1;
  delete [] tk3_2;
  delete [] tk3_3;
}

//tokmas実体表示
void tokmas::tokhyoji() {
  cout << "得意先名[" << tk1 << "]\n";
  cout << "住所 〒[" << tk2 << "]\n";
  cout << "    [" << tk3_1 << "]\n";
  cout << "    [" << tk3_2 << "]\n";
  cout << "    [" << tk3_3 << "]\n";
}

void main(){
  tokmas ssoft("Aufheben Software","郵便番号","住所","番地"," ");
  ssoft.tokhyoji();
}
これは、先の例から、tk1〜tk3を配列ではなくポインターに変更したものです。

tokmas(char* TK1, char* TK2, char* TK3_1, char* TK3_2, char* TK3_3);
配列には実体がないため、メモリの確保が別途必要になります。そこで、クラスの宣言と同時に実体を確保します。そこで、クラス名と同じ名前のメンバー関数をpublicで宣言しておきます。これがコンストラクタです。コンストラクタは、クラスの宣言時に自動的に実行されます。

この場合、new演算子を使ってメモリを確保しています。new演算子は、strlib.hにあるmallocと同じ働きをするものですが、newの方が簡素化した表記ができ、また、stdlib.hを組み込む必要がありません。

なお、コンストラクタには戻り値がありません。したがって、元々voidに決まっているので、voidは書きません。

~tokmas();
確保したメモリは、その関数が終了時あるいは、宣言したクラスを破棄する際に、解放される必要があります。でないと、メモリを無駄にどんどん消費してしまうからです。そういうプログラムは、一見動いているように見えますが、長く使っていると突然メモリーオーバーフローを起こしてしまいます。

そこで、デストラクタを使います。デストラクタは、クラス名の前に〜マークをつけたメンバー関数にします。ここでは、delete演算子を用いてメモリを解放しています。この例では、main()終了前に自動的に呼び出されます。

コンストラクタの多重定義

先の例では、tokmas()コンストラクタのパラメータとして、得意先名、郵便、住所1、住所2、住所3が必要でした。しかし、中には住所を省略したい場合や、全部省略したい場合があります。

そこで、コンストラクタを多重に定義します。これは、同じ名前の、引数の数や型だけが違うメンバー関数を多数用意しておいて、呼び出すときの引数に応じてC++コンパイラの方で自動的に、どの関数を使うかを判断するものです。(オーバーロードといいます)

C言語ではこれが使えないために、strcpyとかstrncpyとか名前の少し違う別の関数を作る必要があり、覚える方も大変でしたが、C++では、このように同じ関数を引数の種類によって使い分ける機能が備わっています。
例)

//何も指定しない
tokmas::tokmas() {
  tk1 = new char[40];
  tk2 = new char[10];
  tk3_1 = new char[30];
  tk3_2 = new char[30];
  tk3_3 = new char[30];
  tk1="未定義";
  tk2=" ";
  tk3_1=" ";
  tk3_2=" ";
  tk3_3=" ";
}

//名前のみ
tokmas::tokmas(char* TK1) {
  tk1 = new char[40];
  tk2 = new char[10];
  tk3_1 = new char[30];
  tk3_2 = new char[30];
  tk3_3 = new char[30];
  strcpy(tk1,TK1);
  tk2=" ";
  tk3_1=" ";
  tk3_2=" ";
  tk3_3=" ";
}

//名前、住所共に指定
tokmas::tokmas(char* TK1, char* TK2, char* TK3_1, char* TK3_2, char* TK3_3) {
  tk1 = new char[40];
  tk2 = new char[10];
  tk3_1 = new char[30];
  tk3_2 = new char[30];
  tk3_3 = new char[30];
  strcpy(tk1,TK1);
  strcpy(tk2,TK2);
  strcpy(tk3_1,TK3_1);
  strcpy(tk3_2,TK3_2);
  strcpy(tk3_3,TK3_3);
}
この場合、tokmasコンストラクタが3種類定義されています。それぞれ、「引数なし」「引数1個」「引数5個」です。この場合、実体を宣言するときの引数の数をコンパイラが判断して、それぞれの型に合ったコンストラクタが使用されるようになっています。

staticデーターメンバー

データーメンバーをグローバル関数(この場合、ファイルスコープという)にする事ができます。
private:
char* tk1;
char* tk2;
char* tk3_1;
char* tk3_2;
char* tk3_3;
static int toksuu;
のように、staticを指定します。この場合、実体は関数の外(ファイルスコープ)で定義します。すると、このtoksuuはtokmasのメンバーではあるものの、実体はグローバル変数になり、別の関数からでも呼び出すことができるようになります。
cout << toksuu << "件のデーターがあります\n";
この例では、得意先の件数をカウントするのに使用しています。

派生クラス

この例では、得意先マスターのクラスとして、名前、住所がありました。では、もし電話番号を追加したい場合、どうすればいいのでしょう?

この場合、元からある得意先マスターのクラスを書き換えたくなるものですが、じゃあ、この得意先マスターが他人の作った、いわゆる『そのプロジェクト全体として使っているクラス』だった場合の事を考えます。

その場合、個人的な事情によりクラスを書き換えるわけにはいきません。かといって、tokmas2とかtokmas3とか別のクラスを1から作り直していたら、元のtokmasクラスが変更があるたびに作り替えねばなりません。

この問題を解決するのが、派生クラスと呼ばれるもので、これは元のクラス(=基本クラス)の機能を継承しつつ、新しい機能を追加したクラスを作成する事ができるものです。
例)

//TOKMASの派生クラス(TOKMAS2)
class tokmas2 : public tokmas {
  private:
  char* tk4;
  public:
  tokmas2(char* TK1, char* TK2, char* TK3_1, char* TK3_2, char* TK3_3, char* TK4);
  ~tokmas2();
  void tokhyoji();
};

//名前、住所、電話番号を指定
tokmas2::tokmas2(char* TK1, char* TK2, char* TK3_1, char* TK3_2, char* TK3_3, char* TK4) :
tokmas(TK1, TK2, TK3_1, TK3_2, TK3_3) { //←基本のコンストラクタを呼ぶ
  tk4=new char[20];
  strcpy(tk4,TK4);
  ++tokmas::toksuu;
  }

//tokmas2のデストラクタ
tokmas2::~tokmas2() {
  delete tk4;
}
//内容表示
void tokmas2::tokhyoji() {
  tokmas::tokhyoji(); //←基本のデーター表示
  cout << "    [" << tk4 << "]\n";
}
この場合、tokmas2という派生クラスを作成し、そこで電話番号付きのコンストラクタを作成しています。

class tokmas2 : public tokmas {
ここで、publicは、基本クラスのデーターメンバーを引き継ぐ事を示しています。他にも以下のようなものがあります。
public 基本クラスのpublicとprotectedを引き継ぐ。
protected 基本クラスのpublicとprotectedを、protectedとして引き継ぐ。
private 基本クラスのpublicとprotectedを、privateとして引き継ぐ。
省略すると、privateが指定されたものとみなされますが、publicを指定する場合の方が多いようです。

tokmas(TK1, TK2, TK3_1, TK3_2, TK3_3) //←基本クラスのコンストラクタを呼ぶ
派生クラスから基本クラスのprivateデーターをアクセスする事はできません。しかし、派生クラスのコンストラクタ実行時に基本クラスのコンストラクタを呼ぶことができます。
派生クラス名::派生コンストラクタ名 : 基本コンストラクタ名 { 中身 }
という風に記述すると、派生クラスのコンストラクタの実行前に、基本クラスのコンストラクタが実行されます。また、派生クラスの実体を破棄するときや、派生クラスを呼んだ関数が終了する前に、基本クラスのデストラクタが実行されます。

void tokmas2::tokhyoji() {
tokmas::tokhyoji(); //←基本クラスのデーター表示


派生クラスから基本クラスの関数を呼ぶことができます。この場合、基本クラスと派生クラスで同じ関数名が使われているので、スコープ解決子(::)を使います。この(::)は、(の)と読みます。

例)
tokmas::tokhyoji()←tokmasの、tokhyouji()を呼ぶ
tokmas2::tokhyoji()←tokmas2の、tokhyouji()を呼ぶ

仮想関数

構造体と同じように、クラスをポインターで表したい時があります。その場合、

tokmas* p;
p=&ssoft;
p->tokhyoji();

みたいにして、構造体(この場合クラス)へのポインターを設定しておいて、−>演算子を使って『現在のポインターが示すメンバー関数を呼ぶ』という風にします。

ところで、この場合は基本クラスだからいいのですが、派生クラスでこのような事をする場合、どうすればいいのでしょう?p->派生クラス名や、p->tokmas2::tokhyoji();はエラーになります。『基本クラスと派生クラスに同じ名前があり、ポインターによって使い分ける』という意味にしたいわけです。そこで使うのが、仮想関数です。先の例でいくと、

void virtual tokhyoji();
という風にして、基本クラスの宣言部にvirtualをつけます。すると、『このメンバー関数は派生クラスにも同様の機能を持つものがありますよ』という意味になります。すると、この場合、

p=&csoft; //←派生クラスへのポインタ
p->tokhyoji(); //←派生クラスのtokhyoji();を呼ぶ

このような事も可能になります。

また、基本クラスのtokhyoji();はまったく使わずに、派生クラスのtokhyoji();だけを使わせたい事があります。これは、派生クラスをいくつも作成しておいて、ポインタに応じて(つまり、初期化したときの型に応じて)、tokhyoji();を使い分けたい場合です。

この場合、基本クラスのtokhyoji();の実体は不要です。そこで、宣言部に=0をつけて、「これは宣言だけですよ」という事をコンパイラに知らせます。

例)
void virtual tokhyoji() =0;

こうすると、tokhyoji()の基本クラスの実体はないので、関数部を記述する必要がなくなります。ただし、必ず派生クラスにtokhyoji()の実体がある必要があります。

thisポインタ

先の例にある、
cout << "得意先名[" << tk1 << "]\n";

は、実際にはtk1は『今操作しているクラスの実体の示す場所にあるtk1』です。たとえば、ssoft->tk1です。しかし、ssoft->と書いてしまうと汎用性がなくなってしまいます。この場合、『今操作中の実体へのポインタ−>tk1』という記述が必要です。

ここでは、暗黙にthisというポインターが使われています。つまり、this->tk1という記述がなされているのと同じ意味になっています。ただし、このthis->はコンパイラが暗黙のうちに使用するので、省略できます。

例)
void tokcpy(tokmas& p);

たとえば、このようなtokmasの実体をコピーする関数があったとします。この場合、tokmasというデーターの実体を引数として使っています。そして、p->tk1;とすると、引数で指定された得意先名になり、this->tk1;とすると、実体として指定された得意先名になります。

例)
ssoft.tokcopy(csoft);

として呼んだ場合、this->tk1がssoftの得意先名、p->tk1がcsoftの得意先名という事になります。また、if (this==p)とする事で、引数として自分自身が指定されたかどうかを検査する事ができます。

フレンド関数

先の例では、tokmasのメンバーである、tk1〜tk3はプライベートなので、他の関数から呼び出す事はできません。しかし、

friend void tokhyoji3();

と書くことで、tokhyoji3();内では、tokmasクラスのプライベートメンバーを自由にアクセスする事ができます。ただし、あまり多用すると、クラスの『データーの保安性を高める』という意味からは遠ざかってしまうので、多用は避けた方が良いと思います。

これは、主に演算子をオーバーロードする場合に用います。たとえば、+演算子をオーバーロードする場合、右辺はそのオブジェクト自身なので、即値が指定された場合に処理できません。しかし、フレンド関数内では、右辺も左辺も引数として処理できるため、右辺が即値の処理ができます。

フレンドクラス

派生クラスに、基本クラスのプライベートメンバーのアクセスを許す事ができます。例えば、
class tokmas {
  (略)
  friend class tokmas2;
};

(略)
class tokmas2 : public tokmas {
  (略)
}
と記述すれば、『tokmas2はtokmasのフレンドクラスである』という意味になり、基本クラス内のプライベート指定のデーターを自由にアクセスできるようになります。
実体を宣言するときに、
const ssoft("Aufheben Software");
みたいにconstをつける事で、関数内で「Aufheben Software」という名前を変更できないようにする事ができます。ただし、これでは、tokmas内のすべてのメンバー関数にアクセスできなくなってしまいます。そこで、『データーを書き換えないメンバー関数には、constをつける』というルールがあります。

例えば、
void tokhyoji() const;

書くことで、『この関数はメンバーデーターの書き換えをしません』という意味になり、const付きのオブジェクトであっても、呼び出すことができるようになります。(constは後ろに付けます)

クラスのコピー

たとえば、
tokmas bsoft("");
tokmas ssoft("Aufheben Software","郵便番号","住所","番地"," ");
bsoft=ssoft;
と書くことで、bsoftの実体に、ssoftの実体がコピーされます。ここでは、メンバーデーターのみがコピーされます。また、

tokmas bsoft=ssoft;

とすると、別のオブジェクトと同じ内容で初期化する事ができます。

ただし、『実体を確保してから中身をコピー』するのと、『別のオブジェクトと同じ内容で初期化』するのは違います。なぜなら、
実体を確保 コピー 初期化
変数 メモリに必要なバイト数が割り当てあれる メモリ→メモリにコピーされる。 実体が指定値になる。
ポインター変数 メモリにポインターを記録する場所が確保される。その後、newでメモリーを確保する。 ポインターの示すアドレスはそのままポインター変数がコピーされる。 ポインターが指定のアドレスになる。
このように、ポインター変数をコピーしても、ポインターの示すアドレスに変化はありません。なので、ポインター変数を別の実体で初期化してしまうと、複数のポインター変数が同じメモリーアドレスを指す事になります。

したがって、
tokmas bsoft=ssoft;

としてしまうと、bsoftとssoftの実体が同じものを指している事になってしまいます。おまけに、デストラクタでdeleteを指定してたりすると、同じメモリを2回解放するという、わけのわかんない状態になるので、ほぼ間違いなくセグメンテーションフォルトになります。

これを解決するために、コピーコンストラクタを使います。

コピーコンストラクタ
関数名にクラス名と同じものを使い、引数にクラスそのものを使ったコンストラクタです。たとえば、

tokmas(tokmas& other);

とします。これは、別のオブジェクトと同じ内容で初期化する指定(たとえば、tokmas bsodt=ssoft;)がされた場合に呼び出されますので、ここで適切な実体を確保する処理を記述します。
例)

//コピーコンストラクタ
tokmas::tokmas(tokmas& other) {
  tk1 = new char[40];
  tk2 = new char[10];
  tk3_1 = new char[30];
  tk3_2 = new char[30];
  tk3_3 = new char[30];
  strcpy(tk1,other.tk1);
  strcpy(tk2,other.tk2);
  strcpy(tk3_1,other.tk3_1);
  strcpy(tk3_2,other.tk3_2);
  strcpy(tk3_3,other.tk3_3);
  ++toksuu;
}
tokmas::tokmas(tokmas& other) {
ここで、コンストラクタですので、関数名はクラス名と同じです。また、引数もクラス名と同じです。違うのは、otherというポインターを使って、初期化値として指定されたメンバーデーターを扱う事ができるという点です。

strcpy(tk1,other.tk1);

で、初期化するtk1(other.tk1)の値を、今確保したtk1に入れます。ここで、手動で(手動という言葉が適切かどうかわからないが、いちおう自分で記述するという事で)初期化してから中身をコピーするという記述を行っています。

これにより、『別のオブジェクトで初期化』が指定された場合でも、実際には『初期化してから中身をコピー』するわけですから、deleteで同じメモリーを2回解放する事もなくなります。

+と=をオーバーロード

もし、クラス同士を足し算する場合どうしたら良いでしょう。この場合、クラス同士を足し算する関数を作ればいいのですが、それを+算術式で表現できれば便利です。そこで、+演算子の機能を拡張します。これを「operator+のオーバーロード」といいます。
例)

tokmas operator+(tokmas &second);

//+演算子のオーバーロード
tokmas tokmas::operator+(tokmas &second) {
  strcpy(ctk1,tk1);
  strcpy(ctk2,tk2);
  strcpy(ctk3_1,tk3_1);
  strcpy(ctk3_2,tk3_2);
  strcpy(ctk3_3,tk3_3);
  strncat(ctk1,second.tk1,40);
  strncat(ctk2,second.tk2,30);
  strncat(ctk3_1,second.tk3_1,30);
  strncat(ctk3_2,second.tk3_2,30);
  strncat(ctk3_3,second.tk3_3,30);
  return tokmas(ctk1, ctk2, ctk3_1, ctk3_2, ctk3_3);
}
tokmas operator+(tokmas &second);
operator+は、「+」という記号の機能を拡張するという意味で、「+」記号自体が関数だと思ってください。これにより、a+bとしたときに、『aというクラスの実体を、bという引数を用いて「+」という関数を処理する』という意味になります。

tokmas tokmas::operator+(tokmas &second) {
以降に、+関数の本体を書きます。この例では、+記号の右側のオブジェクトを、左側のオブジェクトに連結しています。

さて、ここで、c=a+bとしたいのですが、問題があります。それは=演算子はポインターだけを代入し、中身を代入してくれないからです。いや、正式には、=の右辺がクラスだった場合は、ちゃんと中身も代入してくれるのですが、この場合の右辺は『retuenでもとってきたクラス』であり、クラスの中身ではありません。

ですから、=演算子も、クラスの中身をコピーしてくれるように、拡張しなければなりません。
//=演算子のオーバーロード
tokmas tokmas::operator=(tokmas &third) {
  delete [] tk1;
  delete [] tk2;
  delete [] tk3_1;
  delete [] tk3_2;
  delete [] tk3_3;
  tk1 = new char[80];
  tk2 = new char[80];
  tk3_1 = new char[80];
  tk3_2 = new char[80];
  tk3_3 = new char[80];
  strcpy(tk1,third.tk1);
  strcpy(tk2,third.tk2);
  strcpy(tk3_1,third.tk3_1);
  strcpy(tk3_2,third.tk3_2);
  strcpy(tk3_3,third.tk3_3);
  return *this;
}
この場合、まずいったん左辺の中身を破棄しています。そして、新たに領域を確保した後、右辺の内容(third.tk1〜third.tk3_3)をコピーしています。また、最後の「return *this;」は、「今操作しているクラスの実体」という意味です。今操作しているオブジェクトそのものを返値として返す時に使います。これにより、A=B=Cのように=演算子の右辺にさらにオブジェクトを指定した場合にも正しく処理されます。

このように、=演算子のオーバーロードはちょっとやっかいな点があります。同様に、=+や=*をオーバーロードする場合も同様です。ただし、newやdeleteで領域を動的に確保する場合以外では、このような処理は不要です。

ここまで拡張した上で、main()を以下のようにします。
//operator+、operator=の実験
asoft=bsoft+ssoft;
この場合、bsoftの各項目と、ssoftの各項目を連結し、その中身をasoftに代入するという計算です。+と=をオーバーロードしたおかげで、非常にシンプルな演算で文字列の連結を行うことができました。

この場合、得意先の項目ですから、足し算する必要はないでしょうが、これが例えば『得意先別年間売上』の合計を出す場合など、月別に作られたテーブルのそれぞれを足す場合に、簡単な+演算子だけでテーブル同士を足し算することができ、非常にわかりやすいプログラムになります。

最後に

以上がC++の基本的な機能に関する説明です。

C++はC言語を拡張したものですから、C++のコンパイラを使って純粋なC言語だけで作ったプログラムをコンパイルさせる事も可能です。実際、RedHatLinuxに「ソフトウェア開発」パッケージを指定してインストールするとC++コンパイラがインストールされていますが、使われているのはC++の拡張命令を使わないで作られたプログラムばかりです。

Linuxで大切なのは互換性であり、できる限り数多くのハード/CPU/ディストリビューションで同じソースが使えなくてはなりません。そのため、C++拡張命令の使用に消極的になってしまうのは仕方ないと思います。

ただし、『クラス』に関しては非常に便利なだけでなく、プロジェクト単位でクラスを使うように定められている事もあるので、覚えていて損はないと思います。
スポンサーリンク